Compare commits

..

55 Commits

Author SHA1 Message Date
Stef Heyenrath
f73bd5fc4f 1.25.0 2026-01-25 10:08:32 +01:00
Stef Heyenrath
702e156ddc Fix MimePartMatcher and add more tests (#1389)
* mp

* .

* --return

* Fixed

* --

* ...

* fix

* ...

* .
2026-01-24 09:15:43 +01:00
Stef Heyenrath
317fcb1b30 1.24.0 2026-01-18 19:54:34 +01:00
Stef Heyenrath
4b602dd777 Small updates to WireMock.Net.OpenTelemetry 2026-01-18 17:56:07 +01:00
Petr Houška
4525c61847 Add OTEL tracing support for Wiremock + automatic OTEL for Aspire integration (#1418)
* Update aspire to 13.1 (examples + code)

Allows usage of aspire CLI which is very useful for dev in codespaces (for my next PR).

* Add OTEL support

* Initial PR feedback

* PR feedback

* PR feedback

* PR feedback

* Cleanup.

* Cleanup

* Fix

* Fix

* Rename stuff around to be more accurate

* PR feedback

* Update WireMock.Net.OpenTelemetry.csproj

Update <Authors>

* PR feedback parser

* PR feedback package versions

* Status code feedback.

* Update preprocessor directives to to Activity Tracing instead of OpenTelemetry. Is more descriptive.

* Add tests

* Improve tests

---------

Co-authored-by: Stef Heyenrath <Stef.Heyenrath@gmail.com>
2026-01-18 17:22:36 +01:00
Stef Heyenrath
abe996671e Add Copilot Setup Steps action (#1419) 2026-01-09 18:20:12 +01:00
Petr Houška
9f819de696 Update aspire to 13.1 (examples + code) (#1417)
Allows usage of aspire CLI which is very useful for dev in codespaces (for my next PR).
2026-01-09 18:01:45 +01:00
Stef Heyenrath
f5d53453e5 1.23.0 2026-01-05 21:34:11 +01:00
samlatham
0e60e3f3f9 Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers (#1416)
Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers so that optional handlebars helpers can be enabled.

Co-authored-by: Sam Latham <sam.latham@citrix.com>
2026-01-05 21:24:48 +01:00
Luca Ma
9cee6dde00 Pass the parameter matchOperator in Request.WithPath to its inner calls (#1414)
Co-authored-by: Luca Ma <lucama@microsoft.com>
2026-01-04 08:03:19 +01:00
Stef Heyenrath
c88e7378a7 1.22.0 2026-01-02 21:30:59 +01:00
Vadim Hatsura
b090296559 chore(testcontainers): bump up Testcontainers to version 4.10.0 (#1412) 2026-01-02 21:25:28 +01:00
Stef Heyenrath
e5afd69f7c 1.21.0 2025-12-25 15:00:54 +01:00
Stef Heyenrath
f38133d7a4 Fix readyness-check for Testcontainers (#1408)
* Add XUnit Logging to TestcontainersTests

* .
2025-12-25 13:56:29 +01:00
Stef Heyenrath
597c95000e vmImage: 'windows-2025' (#1407) 2025-12-24 16:59:02 +01:00
Stef Heyenrath
4617b99c30 [Collection("Grpc")] 2025-12-24 12:32:56 +01:00
Stef Heyenrath
ffd4d89946 Re-enable TestcontainersTestsGrpc (#1406)
* Re-enable TestcontainersTestsGrpc

* //[Collection("Grpc")]
2025-12-24 12:16:56 +01:00
Stef Heyenrath
2d46c86f47 1.20.0 2025-12-24 10:11:53 +01:00
Stef Heyenrath
75f4fbe9d0 Fix Testcontainers AddProtoDefinition (#1405)
* Fix Testcontainers AddProtoDefinition

* .

* UntilHttpRequestIsSucceeded

* WireMockContainer.ContainerPort

* System.Net/System.Net.Http

* ...

* WithWaitStrategy

* MaxHealthCheckRetries

* for

* _adminApi

* static

* ...

* testOutputHelper.WriteLine("Dumping WireMock logs:");

* Console.WriteLine(

* testOutputHelper.WriteLine("Dumping WireMock.Net mappings:");

* fix WithWaitStrategy

* [Fact]

* <PackageReference Include="ProtoBufJsonConverter" Version="0.11.0" />

* [Collection("Grpc")] / [Fact(Skip = "TODO")]

* ...
2025-12-24 10:09:30 +01:00
Stef Heyenrath
16e3872402 Run the Grpc TestcontainersTests sequential (#1402) 2025-12-21 09:40:16 +01:00
Stef Heyenrath
4c797c328f Add WireMock.Net.NUnit project (#1400)
* Add WireMock.Net.NUnit project

* <Version>0.0.1-preview-01</Version>

* --v
2025-12-20 13:43:54 +01:00
Stef Heyenrath
a5e75a7278 Fix Grpc tests (#1401)
* Fix some Grpc tests

* await Task.Delay(1000);

* ports
2025-12-20 12:08:41 +01:00
Stef Heyenrath
56f65c19e2 Upgrade RamlToOpenApiConverter and YamlDotNet (#1399)
* Upgrade RamlToOpenApiConverter and YamlDotNet

* fix
2025-12-19 18:33:58 +01:00
Stef Heyenrath
6aef4816a5 WireMockServer_WithRequiredClientCertificates_Should_Work_Correct --> IgnoreOnContinuousIntegrationFact 2025-12-19 17:52:20 +01:00
Stef Heyenrath
197a211a52 TestcontainersTests 2025-12-13 11:48:48 +01:00
Stef Heyenrath
3cfeec6035 1.19.0 2025-12-12 11:16:38 +01:00
Stef Heyenrath
b57d5e7548 WireMockContainerBuilder: allow only docker images named wiremock (#1392) 2025-12-11 22:21:39 +01:00
Stef Heyenrath
36b89afce5 fix CI link in Readme 2025-12-11 11:25:28 +01:00
Stef Heyenrath
e2acac55a4 Update WireMockContainerBuilder (WithImage and WithCustomImage) (#1391)
* Update WireMockContainerBuilder (WithImage and WithCustomImage)

* .
2025-12-11 10:55:31 +01:00
Stef Heyenrath
ceabd27ce0 1.18.0 2025-12-09 18:28:28 +01:00
Stef Heyenrath
f8e2c7ee90 Add WithBodyAsType to RequestMatcher (#1388)
* Add WithBody<T>

* .

* t

* t2
2025-12-08 19:15:14 +01:00
Stef Heyenrath
c25d8f33d2 1.17.0 2025-12-07 10:55:07 +01:00
Stef Heyenrath
6da190e596 Aspire: Add WithProtoDefinition to support proto definition at server level (#1383)
* Add property UseHttp2 to WireMockServerArguments

* .

* additionalUrls

* ok?

* WireMockServerArguments

* fx

* AddProtoDefinition

* ...

* FIX

* Always add the lifecycle hook to support dynamic mappings and proto definitions
2025-12-07 10:50:11 +01:00
Stef Heyenrath
44388ce80d Fix random delay in mapping json file (#1386) 2025-11-25 20:54:06 +01:00
Stef Heyenrath
5e25ca767d Fix BuildId (#1384) 2025-11-23 11:19:39 +01:00
Stef Heyenrath
0cc583a4a3 WireMock.Net.xUnit.v3 (netstandard2.0) 2025-11-18 18:52:07 +01:00
Stef Heyenrath
f9633adac1 1.16.0 2025-11-18 18:45:12 +01:00
Stef Heyenrath
37bad618a3 Add WireMock.Net.xUnit.v3 project (#1380)
* Add WireMock.Net.xUnit.v3 project

* .
2025-11-18 18:42:28 +01:00
Johannes Häggqvist
8e69f36f04 Add WireMockHealthCheck in WireMock.Net.Aspire (#1375)
* Add WireMockHealthCheck

For use with Aspire, to make WaitFor(wiremock) more useful.
Calls /__admin/health and checks the result, as well as checks if mappings using AdminApiMappingBuilder has been submitted to the server.

This created a catch-22 problem where the mappings were not submitted until the health check was healthy, but the health check was not healthy until the mappings were submitted.

To avoid this, the WireMockServerLifecycleHook class has been slightly re-arranged, and is now using the AfterEndpointsAllocatedAsync callback rather than the AfterResourcesCreatedAsync callback. Within which a separate Task is created that waits until the server is ready and submits the mappings.

* Move WireMockMappingState to its own file

* Dispose the cancellation tokens in WireMockServerLifecycleHook
2025-11-17 20:14:42 +01:00
Stef Heyenrath
21601889e0 Check if the path is valid when using WithPath(...) (#1377) 2025-11-08 09:02:00 +01:00
Stef Heyenrath
dfeabf228e WireMock.Net.OpenApiParser : support Examples (#1366) 2025-11-08 07:45:38 +01:00
Stef Heyenrath
1feb0ade70 Fix wiki links (#1373)
* Change all links from wiki to documention website

* .

* doc

* ws
2025-10-26 10:13:58 +01:00
Stef Heyenrath
8b1bd1b21b 1.15.0 2025-10-22 10:58:44 +02:00
Michi
c1b23b615e Support Testcontainers 4.8.0 (#1370) 2025-10-22 10:18:16 +02:00
Stef Heyenrath
5885324dfb Fix WithProbability logic (#1367)
* Fix WithProbability logic

* .

* FIX

* Update src/WireMock.Net.Minimal/Owin/MappingMatcher.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 10:16:59 +02:00
Tom Akehurst
e3f3e0a8f2 Tweaked docs message in README 2025-10-13 20:29:29 +01:00
Tom Akehurst
685d28db0b Added link to new doc site from README.md 2025-10-13 20:23:18 +01:00
Stef Heyenrath
f6c5225fe0 1.14.0 2025-10-06 17:00:19 +02:00
Stef Heyenrath
b9019a2f61 Update ProxyUrlReplaceSettingsModel with TransformTemplate property (#1362)
* Update ProxyUrlReplaceSettingsModel with TransformTemplate property + parse settings correctly

* oldValue nullable

* <Version>1.14.0-preview-01</Version>
2025-10-06 09:16:25 +02:00
Stef Heyenrath
b82dad2563 Add Tls13 (#1363)
* Add Tls13

* fix
2025-10-05 15:51:47 +02:00
Stef Heyenrath
45d4e7077d 1.13.0 2025-09-28 12:44:14 +02:00
Stef Heyenrath
19e95325fa ProxyUrlTransformer (#1361)
* ProxyUrlTransformer

* tests

* Update src/WireMock.Net.Shared/Settings/ProxyUrlReplaceSettings.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-28 12:40:33 +02:00
Stef Heyenrath
371bfdc160 TypeLoader: implement Try methods (#1358)
* TypeLoader: implement Try methods

* fix
2025-08-31 08:48:29 +02:00
Stef Heyenrath
5c5e104f2c 1.12.0 2025-08-30 11:49:20 +02:00
Stef Heyenrath
068fdf33e3 Upgrade Testcontainers to 4.7.0 (#1357)
* Upgrade Testcontainers to 4.7.0

* .
2025-08-30 11:46:52 +02:00
197 changed files with 6865 additions and 8607 deletions

View File

@@ -0,0 +1,36 @@
name: "Copilot Setup Steps"
# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest
# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
# If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
contents: read
# You can define any steps you want, and they will run before the agent starts.
# If you do not check out your code, Copilot will do this for you.
steps:
- name: Install .NET 10.x
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.x
dotnet-quality: preview
- name: dotnet --info
run: dotnet --info

View File

@@ -1,3 +1,77 @@
# 1.25.0 (25 January 2026)
- [#1389](https://github.com/wiremock/WireMock.Net/pull/1389) - Fix MimePartMatcher and add more tests [bug] contributed by [StefH](https://github.com/StefH)
- [#1371](https://github.com/wiremock/WireMock.Net/issues/1371) - MimePartMatcher does not match multipart/form-data request body (possible bug?) [bug]
# 1.24.0 (18 January 2026)
- [#1417](https://github.com/wiremock/WireMock.Net/pull/1417) - Update aspire to 13.1 (examples + code) [feature] contributed by [petrroll](https://github.com/petrroll)
- [#1418](https://github.com/wiremock/WireMock.Net/pull/1418) - Add OTEL tracing support for Wiremock + automatic OTEL for Aspire integration [feature] contributed by [petrroll](https://github.com/petrroll)
- [#1214](https://github.com/wiremock/WireMock.Net/issues/1214) - OpenTelemetry Support for .NET Aspire [feature]
# 1.23.0 (05 January 2026)
- [#1414](https://github.com/wiremock/WireMock.Net/pull/1414) - Pass the parameter matchOperator in Request.WithPath to its inner calls [bug] contributed by [gbamqzkdyg](https://github.com/gbamqzkdyg)
- [#1416](https://github.com/wiremock/WireMock.Net/pull/1416) - Fix: Pass AllowedHandlebarsHelpers configuration to Handlebars.Net.Helpers library contributed by [samlatham](https://github.com/samlatham)
- [#1413](https://github.com/wiremock/WireMock.Net/issues/1413) - Parameter `matchOperator` is not respected in the method Request.WithPath [bug]
- [#1415](https://github.com/wiremock/WireMock.Net/issues/1415) - HandlebarsSettings AllowedHandlebarsHelpers Configuration Not Applied [bug]
# 1.22.0 (02 January 2026)
- [#1412](https://github.com/wiremock/WireMock.Net/pull/1412) - chore(testcontainers): bump up Testcontainers to version 4.10.0 [feature] contributed by [vhatsura](https://github.com/vhatsura)
- [#1411](https://github.com/wiremock/WireMock.Net/issues/1411) - WireMock.Net.Testcontainers isn't compatible with Testcontainers 4.10.0 [bug]
# 1.21.0 (25 December 2025)
- [#1408](https://github.com/wiremock/WireMock.Net/pull/1408) - Fix readyness-check for Testcontainers [bug] contributed by [StefH](https://github.com/StefH)
# 1.20.0 (24 December 2025)
- [#1399](https://github.com/wiremock/WireMock.Net/pull/1399) - Upgrade RamlToOpenApiConverter and YamlDotNet [feature] contributed by [StefH](https://github.com/StefH)
- [#1400](https://github.com/wiremock/WireMock.Net/pull/1400) - Add WireMock.Net.NUnit project [feature] contributed by [StefH](https://github.com/StefH)
- [#1405](https://github.com/wiremock/WireMock.Net/pull/1405) - Fix Testcontainers AddProtoDefinition [bug] contributed by [StefH](https://github.com/StefH)
- [#1398](https://github.com/wiremock/WireMock.Net/issues/1398) - Upgrade YamlDotNet dependency [feature]
- [#1404](https://github.com/wiremock/WireMock.Net/issues/1404) - An exception occurs when adding multiple proto definitions in the TestContainer. [bug]
# 1.19.0 (12 December 2025)
- [#1391](https://github.com/wiremock/WireMock.Net/pull/1391) - Update WireMockContainerBuilder (WithImage and WithCustomImage) [feature] contributed by [StefH](https://github.com/StefH)
- [#1392](https://github.com/wiremock/WireMock.Net/pull/1392) - WireMockContainerBuilder: allow all docker images named wiremock [feature] contributed by [StefH](https://github.com/StefH)
- [#1390](https://github.com/wiremock/WireMock.Net/issues/1390) - Unable to build WireMockContainerBuilder with custom image [feature]
# 1.18.0 (09 December 2025)
- [#1388](https://github.com/wiremock/WireMock.Net/pull/1388) - Add WithBodyAsType to RequestMatcher [feature] contributed by [StefH](https://github.com/StefH)
# 1.17.0 (07 December 2025)
- [#1383](https://github.com/wiremock/WireMock.Net/pull/1383) - Aspire: Add WithProtoDefinition to support proto definition at server level [feature] contributed by [StefH](https://github.com/StefH)
- [#1386](https://github.com/wiremock/WireMock.Net/pull/1386) - Fix random delay in mapping json file [bug] contributed by [StefH](https://github.com/StefH)
- [#1274](https://github.com/wiremock/WireMock.Net/issues/1274) - .WithMappings to mount volume is not working for GRPC [bug]
- [#1381](https://github.com/wiremock/WireMock.Net/issues/1381) - Downstream dependencies missing after 1.16.0 release [bug]
- [#1382](https://github.com/wiremock/WireMock.Net/issues/1382) - Does Aspire support enabling HTTP/2? [feature]
- [#1385](https://github.com/wiremock/WireMock.Net/issues/1385) - Do delays and probabilities show in saved static mappings? [bug]
- [#1387](https://github.com/wiremock/WireMock.Net/issues/1387) - Tests failing with TaskCanceledException on Windows Server 2025 Build 7171 [bug]
# 1.16.0 (18 November 2025)
- [#1366](https://github.com/wiremock/WireMock.Net/pull/1366) - WireMock.Net.OpenApiParser : support Examples [feature] contributed by [StefH](https://github.com/StefH)
- [#1375](https://github.com/wiremock/WireMock.Net/pull/1375) - Add WireMockHealthCheck in WireMock.Net.Aspire [feature] contributed by [Zguy](https://github.com/Zguy)
- [#1377](https://github.com/wiremock/WireMock.Net/pull/1377) - Check if the path is valid when using WithPath(...) [feature] contributed by [StefH](https://github.com/StefH)
- [#1380](https://github.com/wiremock/WireMock.Net/pull/1380) - Add WireMock.Net.xUnit.v3 project [feature] contributed by [StefH](https://github.com/StefH)
- [#1364](https://github.com/wiremock/WireMock.Net/issues/1364) - Choosing examples from open api specification for responses. [feature]
- [#1376](https://github.com/wiremock/WireMock.Net/issues/1376) - AdminApiMappingBuilder `WithPath` should add the starting `/` if missing [feature]
- [#1379](https://github.com/wiremock/WireMock.Net/issues/1379) - xUnit v3 [feature]
# 1.15.0 (22 October 2025)
- [#1367](https://github.com/wiremock/WireMock.Net/pull/1367) - Fix WithProbability logic [bug] contributed by [StefH](https://github.com/StefH)
- [#1370](https://github.com/wiremock/WireMock.Net/pull/1370) - Support Testcontainers 4.8.0 [bug] contributed by [MD-V](https://github.com/MD-V)
- [#1126](https://github.com/wiremock/WireMock.Net/issues/1126) - Request matching WithProbability strange behaviour [bug]
# 1.14.0 (06 October 2025)
- [#1362](https://github.com/wiremock/WireMock.Net/pull/1362) - Update ProxyUrlReplaceSettingsModel with TransformTemplate property [feature] contributed by [StefH](https://github.com/StefH)
- [#1363](https://github.com/wiremock/WireMock.Net/pull/1363) - Add Tls13 [bug] contributed by [StefH](https://github.com/StefH)
- [#1342](https://github.com/wiremock/WireMock.Net/issues/1342) - Facing SSL certificate validation error when using .NET 5 and above framework for some http services during recording [bug]
- [#1360](https://github.com/wiremock/WireMock.Net/issues/1360) - ProxyURL path configuration [feature]
# 1.13.0 (28 September 2025)
- [#1358](https://github.com/wiremock/WireMock.Net/pull/1358) - TypeLoader: implement Try methods [feature] contributed by [StefH](https://github.com/StefH)
- [#1361](https://github.com/wiremock/WireMock.Net/pull/1361) - ProxyUrlTransformer [feature] contributed by [StefH](https://github.com/StefH)
# 1.12.0 (30 August 2025)
- [#1357](https://github.com/wiremock/WireMock.Net/pull/1357) - Upgrade Testcontainers to 4.7.0 [feature] contributed by [StefH](https://github.com/StefH)
- [#1356](https://github.com/wiremock/WireMock.Net/issues/1356) - WireMock.Net 1.11.2 is not compatible with TestContainers 4.7.0 [bug]
# 1.11.2 (27 August 2025)
- [#1352](https://github.com/wiremock/WireMock.Net/pull/1352) - Add additional try-catch to TypeLoader logic [feature] contributed by [StefH](https://github.com/StefH)
- [#1354](https://github.com/wiremock/WireMock.Net/pull/1354) - Add System.Net.Http again to solve CVE warning [bug] contributed by [StefH](https://github.com/StefH)

View File

@@ -4,7 +4,7 @@
</PropertyGroup>
<PropertyGroup>
<Version>1.11.2</Version>
<VersionPrefix>1.25.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

View File

@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.11.2
SET version=1.25.0
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%

View File

@@ -15,48 +15,48 @@ Lightweight Http Mocking Server for .NET, inspired by WireMock.org (from the Jav
### :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/wiremock/WireMock.Net/wiki/Stubbing).
See [Stubbing](https://wiremock.org/dotnet/stubbing).
### :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/wiremock/WireMock.Net/wiki/Request-Matching).
WireMock.Net support advanced request-matching logic, see [Request Matching](https://wiremock.org/dotnet/request-matching).
### :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/wiremock/WireMock.Net/wiki/Response-Templating).
The response which is returned WireMock.Net can be changed using templating. This is described here [Response Templating](https://wiremock.org/dotnet/response-templating).
### :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
The WireMock admin API provides functionality to define the mappings via a http interface see [Admin API Reference](https://wiremock.org/dotnet/admin-api-reference).
### :star: Using
WireMock.Net can be used in several ways:
#### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
[UnitTesting](https://wiremock.org/dotnet/using-wiremock-in-unittests).
### Unit/Integration Testing using Testcontainers.DotNet
See [Wiki : WireMock.Net.Testcontainers](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Testcontainers) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
See [WireMock.Net.Testcontainers](https://wiremock.org/dotnet/using-wiremock-net-testcontainers/) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
### Unit/Integration Testing using an an Aspire Distributed Application
See [Wiki : WireMock.Net.Aspire](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
See [WireMock.Net.Aspire](https://wiremock.org/dotnet/using-wiremock-net-Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
#### As a dotnet tool
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
It's simple to install WireMock.Net as (global) dotnet tool, see [dotnet tool](https://wiremock.org/dotnet/wiremock-as-dotnet-tool).
#### As standalone process / console application
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
This is quite straight forward to launch a mock server within a console application, see [Standalone Process](https://wiremock.org/dotnet/wiremock-as-a-standalone-process).
#### As a Windows Service
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
You can also run WireMock.Net as a Windows Service, follow this [Windows Service](https://wiremock.org/dotnet/wiremock-as-a-windows-service).
#### As a Web Job in Azure or application in IIS
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
See this link [WireMock-as-a-(Azure)-Web-App](https://wiremock.org/dotnet/wiremock-as-a-azure-web-app/)
#### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
#### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/wiremock/WireMock.Net/wiki/Using-HTTPS-(SSL))
More details on using HTTPS (SSL) can be found here [HTTPS](https://wiremock.org/dotnet/using-https-ssl/)
## :books: Documentation
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/wiremock/WireMock.Net/wiki/What-Is-WireMock.Net).
For more info, see also this documentation page: [What is WireMock.Net](https://wiremock.org/dotnet/what-is-wiremock-net/).

View File

@@ -1,9 +1,5 @@
# 1.11.2 (27 August 2025)
- #1352 Add additional try-catch to TypeLoader logic [feature]
- #1354 Add System.Net.Http again to solve CVE warning [bug]
- #1355 Revert JetBrains.Annotations and add System.Text.RegularExpressions t&#8230; [bug]
- #1071 Split WireMock into multiple nuget packages depending on features [feature]
- #1351 Version 1.11.0 seems to have a dependency to non-existing version of JetBrans.Annotations [bug]
- #1353 1.11.0: NU1903: Warning As Error: Package 'System.Net.Http' 4.3.0 has a known high severity vulnerability, https://github.com/advisories/GHSA-7jgj-8wvc-jh57 [bug]
# 1.25.0 (25 January 2026)
- #1389 Fix MimePartMatcher and add more tests [bug]
- #1371 MimePartMatcher does not match multipart/form-data request body (possible bug?) [bug]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md

View File

@@ -1,7 +1,11 @@
# ![Project Icon](./resources/logo_32x32.png) WireMock.Net
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics the functionality from the JAVA based [WireMock](http://wiremock.org).
A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) which mimics functionality from the original Java based WireMock.
For more info, see also this WIKI page: [What is WireMock.Net](https://github.com/wiremock/WireMock.Net/wiki/What-Is-WireMock.Net).
---
### :books: Full documentation can now be found at [wiremock.org](https://wiremock.org/dotnet)
---
## :star: Key Features
* HTTP response stubbing, matchable on URL/Path, headers, cookies and body content patterns
@@ -29,7 +33,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/wiremock/WireMock.Net/issues) |
| | |
| ***Quality*** | &nbsp; |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=7) |
| &nbsp;&nbsp;**Build Azure** | [![Build Status Azure](https://stef.visualstudio.com/WireMock.Net/_apis/build/status/WireMock.Net)](https://stef.visualstudio.com/WireMock.Net/_build/latest?definitionId=61) |
| &nbsp;&nbsp;**Quality** | [![Sonar Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=alert_status)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net) [![CodeFactor](https://www.codefactor.io/repository/github/wiremock/wiremock.net/badge)](https://www.codefactor.io/repository/github/wiremock/wiremock.net) |
| &nbsp;&nbsp;**Sonar Bugs** | [![Sonar Bugs](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=bugs)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=BUG) [![Sonar Code Smells](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=code_smells)](https://sonarcloud.io/project/issues?id=WireMock-Net_WireMock.Net&resolved=false&types=CODE_SMELL) |
| &nbsp;&nbsp;**Coverage** | [![Sonar Coverage](https://sonarcloud.io/api/project_badges/measure?project=WireMock-Net_WireMock.Net&metric=coverage)](https://sonarcloud.io/component_measures?id=WireMock-Net_WireMock.Net&metric=coverage) [![codecov](https://codecov.io/gh/wiremock/WireMock.Net/branch/master/graph/badge.svg)](https://codecov.io/gh/wiremock/WireMock.Net)|
@@ -37,7 +41,7 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
### :package: NuGet packages
| | Official | Preview [:information_source:](https://github.com/wiremock/WireMock.Net/wiki/MyGet-preview-versions) |
| | Official | Preview [:information_source:](https://wiremock.org/dotnet/MyGet-preview-versions) |
| - | - | - |
| &nbsp;&nbsp;**WireMock.Net** | [![NuGet Badge WireMock.Net](https://img.shields.io/nuget/v/WireMock.Net)](https://www.nuget.org/packages/WireMock.Net) | [![MyGet Badge WireMock.Net](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net)
| &nbsp;&nbsp;**WireMock.Net.Minimal** 🔺| [![NuGet Badge WireMock.Net.Minimal](https://img.shields.io/nuget/v/WireMock.Net.Minimal)](https://www.nuget.org/packages/WireMock.Net.Minimal) | [![MyGet Badge WireMock.Net](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Minimal?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Minimal)
@@ -49,7 +53,9 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Net.AwesomeAssertions** | [![NuGet Badge WireMock.Net.AwesomeAssertions](https://img.shields.io/nuget/v/WireMock.Net.AwesomeAssertions)](https://www.nuget.org/packages/WireMock.Net.AwesomeAssertions) | [![MyGet Badge WireMock.Net.AwesomeAssertions](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.AwesomeAssertions?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.AwesomeAssertions)
| &nbsp;&nbsp;**WireMock.Net.FluentAssertions** | [![NuGet Badge WireMock.Net.FluentAssertions](https://img.shields.io/nuget/v/WireMock.Net.FluentAssertions)](https://www.nuget.org/packages/WireMock.Net.FluentAssertions) | [![MyGet Badge WireMock.Net.FluentAssertions](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.FluentAssertions?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.FluentAssertions)
| &nbsp;&nbsp;**WireMock.Net.xUnit** | [![NuGet Badge WireMock.Net.xUnit](https://img.shields.io/nuget/v/WireMock.Net.xUnit)](https://www.nuget.org/packages/WireMock.Net.xUnit) | [![MyGet Badge WireMock.Net.xUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.xUnit?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.xUnit)
| &nbsp;&nbsp;**WireMock.Net.xUnit.v3** | [![NuGet Badge WireMock.Net.xUnit](https://img.shields.io/nuget/v/WireMock.Net.xUnit.v3)](https://www.nuget.org/packages/WireMock.Net.xUnit.v3) | [![MyGet Badge WireMock.Net.xUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.xUnit.v3?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.xUnit.v3)
| &nbsp;&nbsp;**WireMock.Net.TUnit** | [![NuGet Badge WireMock.Net.TUnit](https://img.shields.io/nuget/v/WireMock.Net.TUnit)](https://www.nuget.org/packages/WireMock.Net.TUnit) | [![MyGet Badge WireMock.Net.TUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.TUnit?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.TUnit)
| &nbsp;&nbsp;**WireMock.Net.NUnit** | [![NuGet Badge WireMock.Net.NUnit](https://img.shields.io/nuget/v/WireMock.Net.NUnit)](https://www.nuget.org/packages/WireMock.Net.NUnit) | [![MyGet Badge WireMock.Net.TUnit](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.NUnit?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.NUnit)
| | | |
| &nbsp;&nbsp;**WireMock.Net.Extensions.Routing** | [![NuGet Badge WireMock.Net.Extensions.Routing](https://img.shields.io/nuget/v/WireMock.Net.Extensions.Routing)](https://www.nuget.org/packages/WireMock.Net.Extensions.Routing) | [![MyGet Badge WireMock.Net.Extensions.Routing](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Extensions.Routing?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Extensions.Routing)
| &nbsp;&nbsp;**WireMock.Net.Matchers.CSharpCode** | [![NuGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/nuget/v/WireMock.Net.Matchers.CSharpCode)](https://www.nuget.org/packages/WireMock.Net.Matchers.CSharpCode) | [![MyGet Badge WireMock.Net.Matchers.CSharpCode](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.Matchers.CSharpCode?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.Matchers.CSharpCode)
@@ -57,13 +63,14 @@ For more info, see also this WIKI page: [What is WireMock.Net](https://github.co
| &nbsp;&nbsp;**WireMock.Net.MimePart** | [![NuGet Badge WireMock.Net.MimePart](https://img.shields.io/nuget/v/WireMock.Net.MimePart)](https://www.nuget.org/packages/WireMock.Net.MimePart) | [![MyGet Badge WireMock.Net.MimePart](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.MimePart?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.MimePart)
| &nbsp;&nbsp;**WireMock.Net.GraphQL** | [![NuGet Badge WireMock.Net.GraphQL](https://img.shields.io/nuget/v/WireMock.Net.GraphQL)](https://www.nuget.org/packages/WireMock.Net.GraphQL) | [![MyGet Badge WireMock.Net.GraphQL](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.GraphQL?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.GraphQL)
| &nbsp;&nbsp;**WireMock.Net.ProtoBuf** | [![NuGet Badge WireMock.Net.ProtoBuf](https://img.shields.io/nuget/v/WireMock.Net.ProtoBuf)](https://www.nuget.org/packages/WireMock.Net.ProtoBuf) | [![MyGet Badge WireMock.Net.ProtoBuf](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.ProtoBuf?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.ProtoBuf)
| &nbsp;&nbsp;**WireMock.Net.OpenTelemetry** | [![NuGet Badge WireMock.Net.OpenTelemetry](https://img.shields.io/nuget/v/WireMock.Net.OpenTelemetry)](https://www.nuget.org/packages/WireMock.Net.ProtoBuf) | [![MyGet Badge WireMock.Net.OpenTelemetry](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.OpenTelemetry?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.OpenTelemetry)
| | | |
| &nbsp;&nbsp;**WireMock.Net.RestClient** | [![NuGet Badge WireMock.Net.RestClient](https://img.shields.io/nuget/v/WireMock.Net.RestClient)](https://www.nuget.org/packages/WireMock.Net.RestClient) | [![MyGet Badge WireMock.Net.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Net.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Net.RestClient)
| &nbsp;&nbsp;**WireMock.Org.RestClient** | [![NuGet Badge WireMock.Org.RestClient](https://img.shields.io/nuget/v/WireMock.Org.RestClient)](https://www.nuget.org/packages/WireMock.Org.RestClient) | [![MyGet Badge WireMock.Org.RestClient](https://img.shields.io/myget/wiremock-net/vpre/WireMock.Org.RestClient?includePreReleases=true&label=MyGet)](https://www.myget.org/feed/wiremock-net/package/nuget/WireMock.Org.RestClient)
<br />
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL* and *WireMock.Net.ProtoBuf*.
🔺 **WireMock.Net.Minimal** does not include *WireMock.Net.MimePart*, *WireMock.Net.GraphQL*, *WireMock.Net.ProtoBuf* and *WireMock.Net.OpenTelemetry*.
---
@@ -89,52 +96,55 @@ To still enable this feature, you need to add the `Environment` category to the
---
## :memo: Development
For the supported frameworks and build information, see [this](https://github.com/wiremock/WireMock.Net/wiki/Development-Information) page.
For the supported frameworks and build information, see [this](https://wiremock.org/dotnet/development-information) page.
## :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria.
See [Wiki : Stubbing](https://github.com/wiremock/WireMock.Net/wiki/Stubbing).
See [Stubbing](https://wiremock.org/dotnet/stubbing).
## :star: Request Matching
WireMock.Net support advanced request-matching logic, see [Wiki : Request Matching](https://github.com/wiremock/WireMock.Net/wiki/Request-Matching).
WireMock.Net support advanced request-matching logic, see [Request Matching](https://wiremock.org/dotnet/request-matching).
## :star: Response Templating
The response which is returned WireMock.Net can be changed using templating. This is described here [Wiki : Response Templating](https://github.com/wiremock/WireMock.Net/wiki/Response-Templating).
The response which is returned WireMock.Net can be changed using templating. This is described here [Response Templating](https://wiremock.org/dotnet/response-templating).
## :star: Admin API Reference
The WireMock admin API provides functionality to define the mappings via a http interface see [Wiki : Admin API Reference](https://github.com/StefH/WireMock.Net/wiki/Admin-API-Reference).
The WireMock admin API provides functionality to define the mappings via a http interface see [Admin API Reference](https://wiremock.org/dotnet/admin-api-reference).
## :star: Using
WireMock.Net can be used in several ways:
### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see
[Wiki : UnitTesting](https://github.com/StefH/WireMock.Net/wiki/Using-WireMock-in-UnitTests).
[UnitTesting](https://wiremock.org/dotnet/using-wiremock-in-unittests).
### Unit/Integration Testing using Testcontainers.DotNet
See [Wiki : WireMock.Net.Testcontainers](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Testcontainers) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
See [WireMock.Net.Testcontainers](https://wiremock.org/dotnet/using-wiremock-net-testcontainers/) on how to build a WireMock.Net Docker container which can be used in Unit/Integration testing.
### Unit/Integration Testing using an an Aspire Distributed Application
See [Wiki : WireMock.Net.Aspire](https://github.com/wiremock/WireMock.Net/wiki/Using-WireMock.Net.Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
See [WireMock.Net.Aspire](https://wiremock.org/dotnet/using-wiremock-net-Aspire) on how to use WireMock.Net as an Aspire Hosted application to do Unit/Integration testing.
### As a dotnet tool
It's simple to install WireMock.Net as (global) dotnet tool, see [Wiki : dotnet tool](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-dotnet-tool).
It's simple to install WireMock.Net as (global) dotnet tool, see [dotnet tool](https://wiremock.org/dotnet/wiremock-as-dotnet-tool).
### As standalone process / console application
This is quite straight forward to launch a mock server within a console application, see [Wiki : Standalone Process](https://github.com/StefH/WireMock.Net/wiki/WireMock-as-a-standalone-process).
This is quite straight forward to launch a mock server within a console application, see [Standalone Process](https://wiremock.org/dotnet/wiremock-as-a-standalone-process).
### As a Windows Service
You can also run WireMock.Net as a Windows Service, follow this [WireMock-as-a-Windows-Service](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-Windows-Service).
You can also run WireMock.Net as a Windows Service, follow this [Windows Service](https://wiremock.org/dotnet/wiremock-as-a-windows-service).
### As a Web Job in Azure or application in IIS
See this link [WireMock-as-a-(Azure)-Web-App](https://github.com/wiremock/WireMock.Net/wiki/WireMock-as-a-(Azure)-Web-App)
See this link [WireMock-as-a-(Azure)-Web-App](https://wiremock.org/dotnet/wiremock-as-a-azure-web-app/)
### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath).
For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
#### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [Wiki : HTTPS](https://github.com/wiremock/WireMock.Net/wiki/Using-HTTPS-(SSL))
### HTTPS / SSL
More details on using HTTPS (SSL) can be found here [HTTPS](https://wiremock.org/dotnet/using-https-ssl/)
## :books: Documentation
For more info, see also this documentation page: [What is WireMock.Net](https://wiremock.org/dotnet/what-is-wiremock-net/).
---

View File

@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31521.260
# Visual Studio Version 18
VisualStudioVersion = 18.0.11205.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}"
EndProject
@@ -144,212 +144,686 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Extensions.Rou
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ProtoBuf", "src\WireMock.Net.ProtoBuf\WireMock.Net.ProtoBuf.csproj", "{B47413AA-55D3-49A7-896A-17ADBFF72407}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.xUnit.v3", "src\WireMock.Net.xUnit.v3\WireMock.Net.xUnit.v3.csproj", "{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.NUnit", "src\WireMock.Net.NUnit\WireMock.Net.NUnit.csproj", "{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetry", "src\WireMock.Net.OpenTelemetry\WireMock.Net.OpenTelemetry.csproj", "{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.OpenTelemetryDemo", "examples\WireMock.Net.OpenTelemetryDemo\WireMock.Net.OpenTelemetryDemo.csproj", "{9957038D-F9C3-CA5D-E8AE-BE188E512635}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Console.MimePart", "examples\WireMock.Net.Console.MimePart\WireMock.Net.Console.MimePart.csproj", "{4005E20C-D42B-138A-79BE-B3F5420C563F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|x64.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|x64.Build.0 = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|x86.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|x86.Build.0 = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|Any CPU.Build.0 = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|x64.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|x64.Build.0 = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|x86.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|x86.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|x64.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|x86.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|x64.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|x64.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|x86.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|x86.Build.0 = Release|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|x64.Build.0 = Debug|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|x86.Build.0 = Debug|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|x64.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|x64.Build.0 = Release|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|x86.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|x86.Build.0 = Release|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|x64.Build.0 = Debug|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|x86.Build.0 = Debug|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|x64.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|x64.Build.0 = Release|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|x86.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|x86.Build.0 = Release|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|x64.ActiveCfg = Debug|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|x64.Build.0 = Debug|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|x86.ActiveCfg = Debug|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|x86.Build.0 = Debug|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|Any CPU.Build.0 = Release|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|x64.ActiveCfg = Release|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|x64.Build.0 = Release|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|x86.ActiveCfg = Release|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|x86.Build.0 = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|x64.ActiveCfg = Debug|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|x64.Build.0 = Debug|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|x86.ActiveCfg = Debug|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|x86.Build.0 = Debug|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|Any CPU.Build.0 = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x64.ActiveCfg = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x64.Build.0 = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x86.ActiveCfg = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|x86.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x64.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|x86.Build.0 = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x64.Build.0 = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|x86.Build.0 = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|x64.ActiveCfg = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|x64.Build.0 = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|x86.ActiveCfg = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|x86.Build.0 = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.Build.0 = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|x64.ActiveCfg = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|x64.Build.0 = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|x86.ActiveCfg = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|x86.Build.0 = Release|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|x64.ActiveCfg = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|x64.Build.0 = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|x86.ActiveCfg = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|x86.Build.0 = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|Any CPU.Build.0 = Release|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|x64.ActiveCfg = Release|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|x64.Build.0 = Release|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|x86.ActiveCfg = Release|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|x86.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Debug|x64.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Debug|x86.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Release|x64.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Release|x64.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Release|x86.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Release|x86.Build.0 = Release|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Debug|x64.ActiveCfg = Debug|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Debug|x64.Build.0 = Debug|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Debug|x86.ActiveCfg = Debug|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Debug|x86.Build.0 = Debug|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Release|Any CPU.Build.0 = Release|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Release|x64.ActiveCfg = Release|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Release|x64.Build.0 = Release|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Release|x86.ActiveCfg = Release|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Release|x86.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|x64.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|x86.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|x64.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|x64.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|x86.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|x86.Build.0 = Release|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|x64.ActiveCfg = Debug|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|x64.Build.0 = Debug|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|x86.ActiveCfg = Debug|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|x86.Build.0 = Debug|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|Any CPU.Build.0 = Release|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|x64.ActiveCfg = Release|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|x64.Build.0 = Release|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|x86.ActiveCfg = Release|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|x86.Build.0 = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|x64.ActiveCfg = Debug|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|x64.Build.0 = Debug|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|x86.ActiveCfg = Debug|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|x86.Build.0 = Debug|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|Any CPU.Build.0 = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|x64.ActiveCfg = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|x64.Build.0 = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|x86.ActiveCfg = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|x86.Build.0 = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x64.Build.0 = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|x86.Build.0 = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|Any CPU.Build.0 = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x64.ActiveCfg = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x64.Build.0 = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x86.ActiveCfg = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|x86.Build.0 = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|x64.ActiveCfg = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|x64.Build.0 = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|x86.ActiveCfg = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|x86.Build.0 = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Release|Any CPU.Build.0 = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Release|x64.ActiveCfg = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Release|x64.Build.0 = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Release|x86.ActiveCfg = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Release|x86.Build.0 = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Debug|x64.ActiveCfg = Debug|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Debug|x64.Build.0 = Debug|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Debug|x86.ActiveCfg = Debug|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Debug|x86.Build.0 = Debug|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|Any CPU.Build.0 = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|x64.ActiveCfg = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|x64.Build.0 = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|x86.ActiveCfg = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|x86.Build.0 = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x64.ActiveCfg = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x64.Build.0 = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x86.ActiveCfg = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|x86.Build.0 = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.Build.0 = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x64.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x64.Build.0 = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x86.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|x86.Build.0 = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|x64.ActiveCfg = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|x64.Build.0 = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|x86.ActiveCfg = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|x86.Build.0 = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|Any CPU.Build.0 = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|x64.ActiveCfg = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|x64.Build.0 = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|x86.ActiveCfg = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|x86.Build.0 = Release|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Debug|x64.ActiveCfg = Debug|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Debug|x64.Build.0 = Debug|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Debug|x86.ActiveCfg = Debug|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Debug|x86.Build.0 = Debug|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Release|Any CPU.Build.0 = Release|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Release|x64.ActiveCfg = Release|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Release|x64.Build.0 = Release|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Release|x86.ActiveCfg = Release|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Release|x86.Build.0 = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|x64.ActiveCfg = Debug|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|x64.Build.0 = Debug|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|x86.ActiveCfg = Debug|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|x86.Build.0 = Debug|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|Any CPU.Build.0 = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x64.ActiveCfg = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x64.Build.0 = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x86.ActiveCfg = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|x86.Build.0 = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x64.ActiveCfg = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x64.Build.0 = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x86.ActiveCfg = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|x86.Build.0 = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.Build.0 = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x64.ActiveCfg = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x64.Build.0 = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x86.ActiveCfg = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|x86.Build.0 = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x64.ActiveCfg = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x64.Build.0 = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x86.ActiveCfg = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|x86.Build.0 = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|Any CPU.Build.0 = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x64.ActiveCfg = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x64.Build.0 = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x86.ActiveCfg = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|x86.Build.0 = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|x64.ActiveCfg = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|x64.Build.0 = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|x86.ActiveCfg = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|x86.Build.0 = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|Any CPU.Build.0 = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|x64.ActiveCfg = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|x64.Build.0 = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|x86.ActiveCfg = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|x86.Build.0 = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|x64.ActiveCfg = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|x64.Build.0 = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|x86.ActiveCfg = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|x86.Build.0 = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|Any CPU.Build.0 = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|x64.ActiveCfg = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|x64.Build.0 = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|x86.ActiveCfg = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|x86.Build.0 = Release|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Debug|x64.ActiveCfg = Debug|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Debug|x64.Build.0 = Debug|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Debug|x86.ActiveCfg = Debug|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Debug|x86.Build.0 = Debug|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Release|Any CPU.Build.0 = Release|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Release|x64.ActiveCfg = Release|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Release|x64.Build.0 = Release|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Release|x86.ActiveCfg = Release|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Release|x86.Build.0 = Release|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Debug|x64.ActiveCfg = Debug|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Debug|x64.Build.0 = Debug|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Debug|x86.ActiveCfg = Debug|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Debug|x86.Build.0 = Debug|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Release|Any CPU.Build.0 = Release|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Release|x64.ActiveCfg = Release|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Release|x64.Build.0 = Release|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Release|x86.ActiveCfg = Release|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Release|x86.Build.0 = Release|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Debug|x64.ActiveCfg = Debug|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Debug|x64.Build.0 = Debug|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Debug|x86.ActiveCfg = Debug|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Debug|x86.Build.0 = Debug|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Release|Any CPU.Build.0 = Release|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Release|x64.ActiveCfg = Release|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Release|x64.Build.0 = Release|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Release|x86.ActiveCfg = Release|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Release|x86.Build.0 = Release|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Debug|x64.ActiveCfg = Debug|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Debug|x64.Build.0 = Debug|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Debug|x86.ActiveCfg = Debug|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Debug|x86.Build.0 = Debug|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Release|Any CPU.Build.0 = Release|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Release|x64.ActiveCfg = Release|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Release|x64.Build.0 = Release|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Release|x86.ActiveCfg = Release|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Release|x86.Build.0 = Release|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Debug|x64.ActiveCfg = Debug|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Debug|x64.Build.0 = Debug|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Debug|x86.ActiveCfg = Debug|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Debug|x86.Build.0 = Debug|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Release|Any CPU.Build.0 = Release|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Release|x64.ActiveCfg = Release|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Release|x64.Build.0 = Release|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Release|x86.ActiveCfg = Release|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Release|x86.Build.0 = Release|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Debug|x64.ActiveCfg = Debug|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Debug|x64.Build.0 = Debug|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Debug|x86.ActiveCfg = Debug|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Debug|x86.Build.0 = Debug|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Release|Any CPU.Build.0 = Release|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Release|x64.ActiveCfg = Release|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Release|x64.Build.0 = Release|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Release|x86.ActiveCfg = Release|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Release|x86.Build.0 = Release|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Debug|x64.ActiveCfg = Debug|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Debug|x64.Build.0 = Debug|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Debug|x86.ActiveCfg = Debug|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Debug|x86.Build.0 = Debug|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Release|Any CPU.Build.0 = Release|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Release|x64.ActiveCfg = Release|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Release|x64.Build.0 = Release|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Release|x86.ActiveCfg = Release|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Release|x86.Build.0 = Release|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Debug|x64.ActiveCfg = Debug|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Debug|x64.Build.0 = Debug|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Debug|x86.ActiveCfg = Debug|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Debug|x86.Build.0 = Debug|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Release|Any CPU.Build.0 = Release|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Release|x64.ActiveCfg = Release|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Release|x64.Build.0 = Release|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Release|x86.ActiveCfg = Release|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Release|x86.Build.0 = Release|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Debug|x64.ActiveCfg = Debug|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Debug|x64.Build.0 = Debug|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Debug|x86.ActiveCfg = Debug|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Debug|x86.Build.0 = Debug|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Release|Any CPU.Build.0 = Release|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Release|x64.ActiveCfg = Release|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Release|x64.Build.0 = Release|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Release|x86.ActiveCfg = Release|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Release|x86.Build.0 = Release|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Debug|x64.ActiveCfg = Debug|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Debug|x64.Build.0 = Debug|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Debug|x86.ActiveCfg = Debug|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Debug|x86.Build.0 = Debug|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Release|Any CPU.Build.0 = Release|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Release|x64.ActiveCfg = Release|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Release|x64.Build.0 = Release|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Release|x86.ActiveCfg = Release|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Release|x86.Build.0 = Release|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Debug|x64.ActiveCfg = Debug|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Debug|x64.Build.0 = Debug|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Debug|x86.ActiveCfg = Debug|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Debug|x86.Build.0 = Debug|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Release|Any CPU.Build.0 = Release|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Release|x64.ActiveCfg = Release|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Release|x64.Build.0 = Release|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Release|x86.ActiveCfg = Release|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Release|x86.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Debug|x64.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Debug|x86.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Release|x64.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Release|x64.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Release|x86.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Release|x86.Build.0 = Release|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Debug|x64.ActiveCfg = Debug|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Debug|x64.Build.0 = Debug|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Debug|x86.ActiveCfg = Debug|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Debug|x86.Build.0 = Debug|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Release|Any CPU.Build.0 = Release|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Release|x64.ActiveCfg = Release|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Release|x64.Build.0 = Release|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Release|x86.ActiveCfg = Release|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Release|x86.Build.0 = Release|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Debug|x64.Build.0 = Debug|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Debug|x86.ActiveCfg = Debug|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Debug|x86.Build.0 = Debug|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Release|Any CPU.Build.0 = Release|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Release|x64.ActiveCfg = Release|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Release|x64.Build.0 = Release|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Release|x86.ActiveCfg = Release|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Release|x86.Build.0 = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|x64.ActiveCfg = Debug|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|x64.Build.0 = Debug|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|x86.ActiveCfg = Debug|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|x86.Build.0 = Debug|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|Any CPU.Build.0 = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|x64.ActiveCfg = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|x64.Build.0 = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|x86.ActiveCfg = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|x86.Build.0 = Release|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|x64.ActiveCfg = Debug|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|x64.Build.0 = Debug|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|x86.ActiveCfg = Debug|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|x86.Build.0 = Debug|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Release|Any CPU.Build.0 = Release|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Release|x64.ActiveCfg = Release|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Release|x64.Build.0 = Release|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Release|x86.ActiveCfg = Release|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Release|x86.Build.0 = Release|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|x64.ActiveCfg = Debug|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|x64.Build.0 = Debug|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|x86.ActiveCfg = Debug|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|x86.Build.0 = Debug|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|Any CPU.Build.0 = Release|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|x64.ActiveCfg = Release|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|x64.Build.0 = Release|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|x86.ActiveCfg = Release|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|x86.Build.0 = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Debug|x64.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Debug|x64.Build.0 = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Debug|x86.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Debug|x86.Build.0 = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Release|Any CPU.Build.0 = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Release|x64.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Release|x64.Build.0 = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Release|x86.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Release|x86.Build.0 = Release|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|x64.ActiveCfg = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|x64.Build.0 = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|x86.ActiveCfg = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|x86.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.Build.0 = Release|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Release|x64.ActiveCfg = Release|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Release|x64.Build.0 = Release|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Release|x86.ActiveCfg = Release|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Release|x86.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}.Debug|x64.ActiveCfg = Debug|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Debug|x64.Build.0 = Debug|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Debug|x86.ActiveCfg = Debug|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Debug|x86.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
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Release|x64.ActiveCfg = Release|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Release|x64.Build.0 = Release|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Release|x86.ActiveCfg = Release|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Release|x86.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}.Debug|x64.ActiveCfg = Debug|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|x64.Build.0 = Debug|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|x86.ActiveCfg = Debug|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|x86.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
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|x64.ActiveCfg = Release|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|x64.Build.0 = Release|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|x86.ActiveCfg = Release|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|x86.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|x64.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|x64.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|x86.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|x86.Build.0 = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|Any CPU.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|x64.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|x64.Build.0 = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|x86.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|x86.Build.0 = Release|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|x64.ActiveCfg = Debug|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|x64.Build.0 = Debug|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|x86.ActiveCfg = Debug|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|x86.Build.0 = Debug|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|Any CPU.Build.0 = Release|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|x64.ActiveCfg = Release|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|x64.Build.0 = Release|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|x86.ActiveCfg = Release|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|x86.Build.0 = Release|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|x64.ActiveCfg = Debug|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|x64.Build.0 = Debug|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|x86.ActiveCfg = Debug|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|x86.Build.0 = Debug|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|Any CPU.Build.0 = Release|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|x64.ActiveCfg = Release|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|x64.Build.0 = Release|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|x86.ActiveCfg = Release|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|x86.Build.0 = Release|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|x64.ActiveCfg = Debug|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|x64.Build.0 = Debug|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|x86.ActiveCfg = Debug|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|x86.Build.0 = Debug|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|Any CPU.Build.0 = Release|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|x64.ActiveCfg = Release|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|x64.Build.0 = Release|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|x86.ActiveCfg = Release|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|x86.Build.0 = Release|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|x64.ActiveCfg = Debug|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|x64.Build.0 = Debug|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|x86.ActiveCfg = Debug|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Debug|x86.Build.0 = Debug|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|Any CPU.Build.0 = Release|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|x64.ActiveCfg = Release|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|x64.Build.0 = Release|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|x86.ActiveCfg = Release|Any CPU
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067}.Release|x86.Build.0 = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|x64.ActiveCfg = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|x64.Build.0 = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|x86.ActiveCfg = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Debug|x86.Build.0 = Debug|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|Any CPU.Build.0 = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|x64.ActiveCfg = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|x64.Build.0 = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|x86.ActiveCfg = Release|Any CPU
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982}.Release|x86.Build.0 = Release|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Debug|x64.ActiveCfg = Debug|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Debug|x64.Build.0 = Debug|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Debug|x86.ActiveCfg = Debug|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Debug|x86.Build.0 = Debug|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Release|Any CPU.Build.0 = Release|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Release|x64.ActiveCfg = Release|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Release|x64.Build.0 = Release|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Release|x86.ActiveCfg = Release|Any CPU
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}.Release|x86.Build.0 = Release|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Debug|x64.ActiveCfg = Debug|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Debug|x64.Build.0 = Debug|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Debug|x86.ActiveCfg = Debug|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Debug|x86.Build.0 = Debug|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|Any CPU.Build.0 = Release|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x64.ActiveCfg = Release|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x64.Build.0 = Release|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x86.ActiveCfg = Release|Any CPU
{9957038D-F9C3-CA5D-E8AE-BE188E512635}.Release|x86.Build.0 = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x64.ActiveCfg = Debug|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x64.Build.0 = Debug|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x86.ActiveCfg = Debug|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Debug|x86.Build.0 = Debug|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|Any CPU.Build.0 = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x64.ActiveCfg = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x64.Build.0 = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x86.ActiveCfg = Release|Any CPU
{4005E20C-D42B-138A-79BE-B3F5420C563F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -407,6 +881,11 @@ Global
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C} = {0BB8B634-407A-4610-A91F-11586990767A}
{1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{B47413AA-55D3-49A7-896A-17ADBFF72407} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{4F46BD02-BEBC-4B2D-B857-4169AD1FB067} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{2DBBD70D-8051-441F-92BB-FF9B8B4B4982} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{9957038D-F9C3-CA5D-E8AE-BE188E512635} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{4005E20C-D42B-138A-79BE-B3F5420C563F} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -100,7 +100,7 @@ jobs:
- job: Windows_Build_Test
pool:
vmImage: 'windows-2022'
vmImage: 'windows-2025'
steps:
- task: UseDotNet@2
@@ -141,9 +141,13 @@ jobs:
dependsOn: Windows_Build_Test
pool:
vmImage: 'windows-2022'
vmImage: 'windows-2025'
steps:
- script: |
echo "BuildId = $(buildId)"
displayName: 'Print buildId'
- task: UseDotNet@2
displayName: Use .NET 8.0
inputs:

View File

@@ -1,5 +1,5 @@
pool:
vmImage: 'windows-2022'
vmImage: 'windows-2025'
variables:
Prerelease: ''

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -18,7 +18,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup>
</Project>
<ItemGroup>
<None Update="__admin\mappings\*.proto">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -4,14 +4,28 @@ var builder = DistributedApplication.CreateBuilder(args);
// IResourceBuilder<ProjectResource> apiService = builder.AddProject<Projects.AspireApp1_ApiService>("apiservice");
var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "WireMockMappings");
var mappingsPath = Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings");
IResourceBuilder<WireMockServerResource> apiService = builder
.AddWireMock("apiservice", WireMockServerArguments.DefaultPort)
//IResourceBuilder<WireMockServerResource> apiService1 = builder
// //.AddWireMock("apiservice", WireMockServerArguments.DefaultPort)
// .AddWireMock("apiservice1", "http://*:8081", "grpc://*:9091")
// .AsHttp2Service()
// .WithMappingsPath(mappingsPath)
// .WithReadStaticMappings()
// .WithWatchStaticMappings()
// .WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync);
IResourceBuilder<WireMockServerResource> apiService2 = builder
.AddWireMock("apiservice", async args =>
{
args.WithAdditionalUrls("http://*:8081", "grpc://*:9093");
args.WithProtoDefinition("my-greeter", await File.ReadAllTextAsync(Path.Combine(mappingsPath, "greet.proto")));
})
.AsHttp2Service()
.WithMappingsPath(mappingsPath)
.WithReadStaticMappings()
.WithWatchStaticMappings()
.WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync);
.WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync)
.WithOpenTelemetry(); // Enable OpenTelemetry tracing for Aspire dashboard
//var apiServiceUsedForDocs = builder
// .AddWireMock("apiservice1", WireMockServerArguments.DefaultPort)
@@ -45,6 +59,7 @@ IResourceBuilder<WireMockServerResource> apiService = builder
builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
.WithExternalHttpEndpoints()
.WithReference(apiService);
.WithReference(apiService2)
.WaitFor(apiService2);
builder.Build().Run();
await builder.Build().RunAsync();

View File

@@ -0,0 +1,21 @@
syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
enum PhoneType {
none = 0;
mobile = 1;
home = 2;
}
PhoneType phoneType = 2;
}

View File

@@ -0,0 +1,40 @@
{
"Guid": "351f0240-bba0-4bcb-93c6-1feba0fe0004",
"Title": "ProtoBuf Mapping 4",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/greet.Greeter/SayHello",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"Matcher": {
"Name": "ProtoBufMatcher",
"ProtoBufMessageType": "greet.HelloRequest"
}
}
},
"Response": {
"BodyAsJson": {
"message": "hello {{request.BodyAsJson.name}} {{request.method}}"
},
"UseTransformer": true,
"TransformerType": "Handlebars",
"TransformerReplaceNodeOptions": "EvaluateAndTryToConvert",
"Headers": {
"Content-Type": "application/grpc"
},
"TrailingHeaders": {
"grpc-status": "0"
},
"ProtoBufMessageType": "greet.HelloReply"
},
"ProtoDefinition": "my-greeter"
}

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup>
</Project>

View File

@@ -6,4 +6,4 @@ builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
.WithExternalHttpEndpoints()
.WithReference(apiService);
builder.Build().Run();
await builder.Build().RunAsync();

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="8.0.0" />
<PackageReference Include="Aspire.Hosting.Testing" Version="13.1.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.5.3" />

View File

@@ -0,0 +1,82 @@
// Copyright © WireMock.Net
using Newtonsoft.Json;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
namespace WireMock.Net.Console.MimePart;
// Test this CURL:
// curl -X POST http://localhost:9091/multipart -F "plainText=This is some plain text;type=text/plain" -F "jsonData={ `"Key`": `"Value`" };type=application/json" -F "image=@image.png;type=image/png"
//
// curl -X POST http://localhost:9091/multipart2 -F "plainText=This is some plain text;type=text/plain" -F "jsonData={ `"Key`": `"Value`" };type=application/json" -F "image=@image.png;type=image/png"
public static class MainApp
{
public static async Task RunAsync()
{
using var server = WireMockServer.Start(new WireMockServerSettings
{
Port = 9091,
StartAdminInterface = true,
ReadStaticMappings = true,
//WatchStaticMappings = true,
//WatchStaticMappingsInSubdirectories = true,
Logger = new WireMockConsoleLogger()
});
System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));
var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain");
var textPlainContentMatcher = new ExactMatcher("This is some plain text");
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
var textJsonContentTypeMatcher = new ContentTypeMatcher("application/json");
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);
var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png");
var imagePngContentDispositionMatcher = new ExactMatcher("form-data; name=\"image\"; filename=\"image.png\"");
var imagePngContentTransferEncodingMatcher = new ExactMatcher("default");
var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"));
var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher);
var matchers = new IMatcher[]
{
textPlainMatcher,
textJsonMatcher,
imagePngMatcher
};
server
.Given(Request.Create()
.WithPath("/multipart")
.UsingPost()
.WithMultiPart(matchers)
)
.WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb")
.RespondWith(Response.Create()
.WithBody("MultiPart is ok")
);
// server.SaveStaticMappings();
System.Console.WriteLine(JsonConvert.SerializeObject(server.MappingModels, Formatting.Indented));
System.Console.WriteLine("Press any key to stop the server");
System.Console.ReadKey();
server.Stop();
System.Console.WriteLine("Displaying all requests");
var allRequests = server.LogEntries;
System.Console.WriteLine(JsonConvert.SerializeObject(allRequests, Formatting.Indented));
System.Console.WriteLine("Press any key to quit");
System.Console.ReadKey();
}
}

View File

@@ -0,0 +1,23 @@
// Copyright © WireMock.Net
using System.Reflection;
using log4net;
using log4net.Config;
using log4net.Repository;
namespace WireMock.Net.Console.MimePart;
static class Program
{
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
static async Task Main(params string[] args)
{
Log.Info("Starting WireMock.Net.Console.MimePart...");
XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config"));
await MainApp.RunAsync();
}
}

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="__admin\mappings\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="log4net.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,79 @@
{
"Guid": "b9c82182-e469-41da-bcaf-b6e3157fefdc",
"UpdatedAt": "2025-12-18T17:21:57.3879723Z",
"Request": {
"Path": {
"Matchers": [
{
"Name": "WildcardMatcher",
"Pattern": "/multipart2",
"IgnoreCase": false
}
]
},
"Methods": [
"POST"
],
"Body": {
"MatcherName": "MultiPartMatcher",
"Matchers": [
{
"Name": "MimePartMatcher",
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "text/plain",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "ExactMatcher",
"Pattern": "This is some plain text",
"IgnoreCase": false
}
},
{
"Name": "MimePartMatcher",
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "application/json",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "JsonMatcher",
"Pattern": {
"Key": "Value"
},
"IgnoreCase": true,
"Regex": false
}
},
{
"Name": "MimePartMatcher",
"ContentTypeMatcher": {
"Name": "ContentTypeMatcher",
"Pattern": "image/png",
"IgnoreCase": false
},
"ContentDispositionMatcher": {
"Name": "ExactMatcher",
"Pattern": "form-data; name=\"image\"; filename=\"image.png\"",
"IgnoreCase": false
},
"ContentTransferEncodingMatcher": {
"Name": "ExactMatcher",
"Pattern": "default",
"IgnoreCase": false
},
"ContentMatcher": {
"Name": "ExactObjectMatcher",
"Pattern": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"
}
}
],
"MatchOperator": "Or"
}
},
"Response": {
"BodyDestination": "SameAsSource",
"Body": "MultiPart2 is ok"
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
</configSections>
<appSettings>
<add key="log4net.Internal.Debug" value="true"/>
</appSettings>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger{1} - %message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
</configuration>

View File

@@ -7,7 +7,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -267,7 +267,7 @@ namespace WireMock.Net.ConsoleApplication
}
}
public static void Run()
public static async Task RunAsync()
{
//RunSse();
//RunOnLocal();
@@ -290,25 +290,56 @@ namespace WireMock.Net.ConsoleApplication
var server = WireMockServer.Start();
//server
// .Given(Request.Create()
// .WithPath("todos")
// .UsingGet()
// )
// .RespondWith(Response.Create()
// .WithBodyAsJson(todos.Values)
// );
//server
// .Given(Request.Create()
// .UsingGet()
// .WithPath("todos")
// .WithParam("id")
// )
// .RespondWith(Response.Create()
// .WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())])
// );
var pX = 0.80;
server
.Given(Request.Create()
.WithPath("todos")
.UsingGet()
)
.RespondWith(Response.Create()
.WithBodyAsJson(todos.Values)
);
.Given(Request.Create().UsingGet().WithPath("/p"))
.WithProbability(pX)
.RespondWith(Response.Create().WithStatusCode(200).WithBody("X"));
server
.Given(Request.Create()
.UsingGet()
.WithPath("todos")
.WithParam("id")
)
.RespondWith(Response.Create()
.WithBodyAsJson(rm => todos[int.Parse(rm.Query!["id"].ToString())])
);
.Given(Request.Create().UsingGet().WithPath("/p"))
.RespondWith(Response.Create().WithStatusCode(200).WithBody("default"));
// Act
var requestUri = new Uri($"http://localhost:{server.Port}/p");
var c = server.CreateClient();
var xCount = 0;
var defaultCount = 0;
var tot = 1000;
for (var i = 0; i < tot; i++)
{
var response = await c.GetAsync(requestUri);
var value = await response.Content.ReadAsStringAsync();
if (value == "X")
{
xCount++;
}
else if (value == "default")
{
defaultCount++;
}
}
System.Console.WriteLine("X = {0} ; default = {1} ; pX = {2:0.00} ; valueX = {3:0.00}", xCount, defaultCount, pX, 1.0 * xCount / tot);
using var httpAndHttpsWithPort = WireMockServer.Start(new WireMockServerSettings
{
HostingScheme = HostingScheme.HttpAndHttps,
@@ -525,39 +556,6 @@ namespace WireMock.Net.ConsoleApplication
);
#endif
#if MIMEKIT
var textPlainContentTypeMatcher = new ContentTypeMatcher("text/plain");
var textPlainContentMatcher = new ExactMatcher("This is some plain text");
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
var textJsonContentTypeMatcher = new ContentTypeMatcher("text/json");
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
var textJsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);
var imagePngContentTypeMatcher = new ContentTypeMatcher("image/png");
var imagePngContentDispositionMatcher = new ExactMatcher("attachment; filename=\"image.png\"");
var imagePngContentTransferEncodingMatcher = new ExactMatcher("base64");
var imagePngContentMatcher = new ExactObjectMatcher(Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC"));
var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, imagePngContentTypeMatcher, imagePngContentDispositionMatcher, imagePngContentTransferEncodingMatcher, imagePngContentMatcher);
var matchers = new IMatcher[]
{
textPlainMatcher,
textJsonMatcher,
imagePngMatcher
};
server
.Given(Request.Create()
.WithPath("/multipart")
.UsingPost()
.WithMultiPart(matchers)
)
.WithGuid("b9c82182-e469-41da-bcaf-b6e3157fefdb")
.RespondWith(Response.Create()
.WithBody("MultiPart is ok")
);
#endif
// 400 ms
server
.Given(Request.Create()

View File

@@ -2,6 +2,7 @@
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using log4net;
using log4net.Config;
using log4net.Repository;
@@ -14,10 +15,10 @@ static class Program
private static readonly ILoggerRepository LogRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
private static readonly ILog Log = LogManager.GetLogger(typeof(Program));
static void Main(params string[] args)
static async Task Main(params string[] args)
{
XmlConfigurator.Configure(LogRepository, new FileInfo("log4net.config"));
MainApp.Run();
await MainApp.RunAsync();
}
}

View File

@@ -0,0 +1,142 @@
// Copyright © WireMock.Net
// OpenTelemetry Tracing Demo for WireMock.Net
// This demo uses the Console Exporter to visualize traces in the terminal.
using OpenTelemetry;
using OpenTelemetry.Trace;
using WireMock.OpenTelemetry;
using WireMock.Server;
using WireMock.Settings;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
Console.WriteLine("=== WireMock.Net OpenTelemetry Tracing Demo ===\n");
// WireMock.Net creates Activity objects using System.Diagnostics.Activity (built into .NET).
// These activities are automatically created when ActivityTracingEnabled is set to true.
//
// To export these traces, you have two options:
//
// Option 1: Configure your own TracerProvider (shown below)
// - Full control over exporters (Console, OTLP, Jaeger, etc.)
// - Add additional instrumentation (HttpClient, database, etc.)
// - Recommended for most applications
//
// Option 2: Use WireMock.Net.OpenTelemetry package
// - Reference the WireMock.Net.OpenTelemetry NuGet package
// - Use services.AddWireMockOpenTelemetry(openTelemetryOptions)
// - Adds WireMock + ASP.NET Core instrumentation and OTLP exporter
// - Good for quick setup with all-in-one configuration
// Option 1: Custom TracerProvider with Console exporter for this demo
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddWireMockInstrumentation(new OpenTelemetryOptions() { ExcludeAdminRequests = true })
.AddHttpClientInstrumentation() // HTTP client traces (for our test requests)
.AddConsoleExporter() // Export traces to console for demo purposes
.AddOtlpExporter() // Export to real OTLP collector (e.g. Jaeger, Tempo, etc.)
.Build();
Console.WriteLine("Console Exporter configured to visualize:");
Console.WriteLine(" - WireMock.Net traces (wiremock.* tags)");
Console.WriteLine(" - ASP.NET Core server traces");
Console.WriteLine(" - HTTP client traces\n");
// Start WireMock server with OpenTelemetry enabled (ActivityTracingOptions != null enables tracing)
var server = WireMockServer.Start(new WireMockServerSettings
{
StartAdminInterface = true,
ActivityTracingOptions = new ActivityTracingOptions
{
ExcludeAdminRequests = true
}
});
Console.WriteLine($"WireMock server started at: {string.Join(", ", server.Urls)}\n");
// Configure some mock mappings
server
.Given(Request.Create()
.WithPath("/api/hello")
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithBody("Hello from WireMock!"));
server
.Given(Request.Create()
.WithPath("/api/user/*")
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody(@"{""name"": ""John Doe"", ""email"": ""john@example.com""}"));
server
.Given(Request.Create()
.WithPath("/api/error")
.UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(500)
.WithBody("Internal Server Error"));
Console.WriteLine("Mock mappings configured:");
Console.WriteLine(" GET /api/hello -> 200 OK");
Console.WriteLine(" GET /api/user/* -> 200 OK (JSON)");
Console.WriteLine(" GET /api/error -> 500 Error");
Console.WriteLine();
// Make some test requests to generate traces
using var httpClient = server.CreateClient();
Console.WriteLine("Making test requests to generate traces...\n");
Console.WriteLine("─────────────────────────────────────────────────────────────────");
// Request 1: Successful request
Console.WriteLine("\n>>> Request 1: GET /api/hello");
var response1 = await httpClient.GetAsync("/api/hello");
Console.WriteLine($"<<< Response: {(int)response1.StatusCode} {response1.StatusCode}");
Console.WriteLine($" Body: {await response1.Content.ReadAsStringAsync()}");
await Task.Delay(500); // Small delay to let trace export complete
// Request 2: Another successful request with path parameter
Console.WriteLine("\n>>> Request 2: GET /api/user/123");
var response2 = await httpClient.GetAsync("/api/user/123");
Console.WriteLine($"<<< Response: {(int)response2.StatusCode} {response2.StatusCode}");
Console.WriteLine($" Body: {await response2.Content.ReadAsStringAsync()}");
await Task.Delay(500);
// Request 3: Error response
Console.WriteLine("\n>>> Request 3: GET /api/error");
var response3 = await httpClient.GetAsync("/api/error");
Console.WriteLine($"<<< Response: {(int)response3.StatusCode} {response3.StatusCode}");
Console.WriteLine($" Body: {await response3.Content.ReadAsStringAsync()}");
await Task.Delay(500);
// Request 4: No matching mapping (404)
Console.WriteLine("\n>>> Request 4: GET /api/notfound");
var response4 = await httpClient.GetAsync("/api/notfound");
Console.WriteLine($"<<< Response: {(int)response4.StatusCode} {response4.StatusCode}");
await Task.Delay(500);
// Request 5: Admin API request (should be excluded from tracing)
Console.WriteLine("\n>>> Request 5: GET /__admin/health");
var response5 = await httpClient.GetAsync("/__admin/health");
Console.WriteLine($"<<< Admin Health Status: {response5.StatusCode}");
Console.WriteLine("\n─────────────────────────────────────────────────────────────────");
Console.WriteLine("\nTraces above show OpenTelemetry activities from WireMock.Net!");
Console.WriteLine("Look for 'Activity.TraceId', 'Activity.SpanId', and custom tags like:");
Console.WriteLine(" - http.request.method");
Console.WriteLine(" - url.path");
Console.WriteLine(" - http.response.status_code");
Console.WriteLine(" - wiremock.mapping.matched");
Console.WriteLine(" - wiremock.mapping.guid");
Console.WriteLine();
// Cleanup
server.Stop();
Console.WriteLine("WireMock server stopped. Demo complete!");

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net.OpenTelemetry\WireMock.Net.OpenTelemetry.csproj" />
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
</Project>

View File

@@ -8,6 +8,12 @@ namespace WireMock.Admin.Mappings;
[FluentBuilder.AutoGenerateBuilder]
public class BodyModel
{
/// <summary>
/// The name of the body matcher.
/// Currently only "MultiPartMatcher" is supported.
/// </summary>
public string? MatcherName { get; set; }
/// <summary>
/// Gets or sets the matcher.
/// </summary>

View File

@@ -22,7 +22,7 @@ public class MatcherModel
public object? Pattern { get; set; }
/// <summary>
/// Gets or sets the patterns. Can be array of strings (default) or an array of objects.
/// Gets or sets the patterns. Can be an array of strings (default) or an array of objects.
/// </summary>
public object[]? Patterns { get; set; }

View File

@@ -24,4 +24,13 @@ public class StatusModel
/// The error message.
/// </summary>
public string? Error { get; set; }
/// <summary>
/// Returns a string that represents the current status model, including its unique identifier, status, and error information.
/// </summary>
/// <returns>A string containing the values of the Guid, Status, and Error properties formatted for display.</returns>
public override string ToString()
{
return $"StatusModel [Guid={Guid}, Status={Status}, Error={Error}]";
}
}

View File

@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using WireMock.Matchers.Request;
namespace WireMock.Admin.Requests;
@@ -47,5 +48,5 @@ public class LogRequestMatchModel
/// <value>
/// The match details.
/// </value>
public IList<object> MatchDetails { get; set; }
public IList<MatchDetail> MatchDetails { get; set; } = [];
}

View File

@@ -1,5 +1,7 @@
// Copyright © WireMock.Net
using WireMock.Types;
namespace WireMock.Admin.Settings;
/// <summary>
@@ -11,15 +13,25 @@ public class ProxyUrlReplaceSettingsModel
/// <summary>
/// The old path value to be replaced by the new path value
/// </summary>
public string OldValue { get; set; } = null!;
public string? OldValue { get; set; }
/// <summary>
/// The new path value to replace the old value with
/// </summary>
public string NewValue { get; set; } = null!;
public string? NewValue { get; set; }
/// <summary>
/// Defines if the case should be ignore when replacing.
/// Defines if the case should be ignored when replacing.
/// </summary>
public bool IgnoreCase { get; set; }
/// <summary>
/// Holds the transformation template.
/// </summary>
public string? TransformTemplate { get; set; }
/// <summary>
/// The transformer type.
/// </summary>
public TransformerType TransformerType { get; set; } = TransformerType.Handlebars;
}

View File

@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using System;
using WireMock.Validators;
// ReSharper disable once CheckNamespace
namespace WireMock.Admin.Mappings;
@@ -94,9 +95,14 @@ public partial class RequestModelBuilder
}
/// <summary>
/// Set the Path.
/// Set the Path. Must start with a forward slash (/).
/// </summary>
public RequestModelBuilder WithPath(string value) => WithPath(() => value);
public RequestModelBuilder WithPath(string value)
{
PathValidator.ValidateAndThrow(value);
return WithPath(() => value);
}
/// <summary>
/// Set the Path.

View File

@@ -55,4 +55,11 @@ public interface IRequestMatchResult : IComparable
/// <param name="exception">The exception [Optional].</param>
/// <returns>The score.</returns>
double AddScore(Type matcherType, double score, Exception? exception);
/// <summary>
/// Adds the score.
/// </summary>
/// <param name="matchDetail">The matchDetail.</param>
/// <returns>The score.</returns>
double AddMatchDetail(MatchDetail matchDetail);
}

View File

@@ -12,7 +12,12 @@ public class MatchDetail
/// <summary>
/// Gets or sets the type of the matcher.
/// </summary>
public Type MatcherType { get; set; } = null!;
public required Type MatcherType { get; set; }
/// <summary>
/// Gets or sets the type of the matcher.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Gets or sets the score between 0.0 and 1.0
@@ -24,4 +29,9 @@ public class MatchDetail
/// [Optional]
/// </summary>
public Exception? Exception { get; set; }
/// <summary>
/// The child MatchResults in case of multiple matchers.
/// </summary>
public MatchDetail[]? MatchDetails { get; set; }
}

View File

@@ -0,0 +1,19 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.Validators;
public static class PathValidator
{
/// <summary>
/// A valid path must start with a '/' and cannot be null, empty or whitespace.
/// </summary>
public static void ValidateAndThrow(string? path, string? paramName = null)
{
if (string.IsNullOrWhiteSpace(path) || path?.StartsWith("/") == false)
{
throw new ArgumentException("Path must start with a '/' and cannot be null, empty or whitespace.", paramName ?? nameof(path));
}
}
}

View File

@@ -30,7 +30,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\WireMock.Net.Minimal\Util\EnhancedFileSystemWatcher.cs" Link="Utils\EnhancedFileSystemWatcher.cs" />
<Compile Include="..\WireMock.Net.Minimal\Util\EnhancedFileSystemWatcher.cs" Link="Util\EnhancedFileSystemWatcher.cs" />
<Compile Include="..\WireMock.Net.Minimal\Constants\WireMockConstants.cs" Link="Constants\WireMockConstants.cs" />
<Compile Include="..\WireMock.Net.Shared\Constants\RegexConstants.cs" Link="Constants\RegexConstants.cs" />
<Compile Include="..\WireMock.Net.Minimal\Util\PortUtils.cs" Link="Util\PortUtils.cs" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
@@ -42,7 +45,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting" Version="13.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,44 @@
// Copyright © WireMock.Net
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using WireMock.Client;
namespace WireMock.Net.Aspire;
/// <summary>
/// WireMockHealthCheck
/// </summary>
public class WireMockHealthCheck(WireMockServerResource resource) : IHealthCheck
{
private const string HealthStatusHealthy = "Healthy";
/// <inheritdoc />
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
if (!await IsHealthyAsync(resource.AdminApi.Value, cancellationToken))
{
return HealthCheckResult.Unhealthy("WireMock.Net is not healthy");
}
if (resource.ApiMappingState == WireMockMappingState.NotSubmitted)
{
return HealthCheckResult.Unhealthy("WireMock.Net has not received mappings");
}
return HealthCheckResult.Healthy();
}
private static async Task<bool> IsHealthyAsync(IWireMockAdminApi adminApi, CancellationToken cancellationToken)
{
try
{
var status = await adminApi.GetHealthAsync(cancellationToken);
return string.Equals(status, HealthStatusHealthy, StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
}

View File

@@ -0,0 +1,12 @@
// Copyright © WireMock.Net
namespace WireMock.Net.Aspire;
internal enum WireMockMappingState
{
NoMappings = 0,
NotSubmitted = 1,
Submitted = 2
}

View File

@@ -1,7 +1,9 @@
// Copyright © WireMock.Net
using System.Diagnostics.CodeAnalysis;
using Stef.Validation;
using WireMock.Client.Builders;
using WireMock.Util;
// ReSharper disable once CheckNamespace
namespace Aspire.Hosting;
@@ -21,10 +23,15 @@ public class WireMockServerArguments
private const string DefaultLogger = "WireMockConsoleLogger";
/// <summary>
/// The HTTP port where WireMock.Net is listening.
/// The HTTP ports where WireMock.Net is listening on.
/// If not defined, .NET Aspire automatically assigns a random port.
/// </summary>
public int? HttpPort { get; set; }
public List<int> HttpPorts { get; set; } = [];
/// <summary>
/// Additional Urls on which WireMock listens.
/// </summary>
public List<string> AdditionalUrls { get; set; } = [];
/// <summary>
/// The admin username.
@@ -67,6 +74,58 @@ public class WireMockServerArguments
/// </summary>
public Func<AdminApiMappingBuilder, CancellationToken, Task>? ApiMappingBuilder { get; set; }
/// <summary>
/// Grpc ProtoDefinitions.
/// </summary>
public Dictionary<string, string[]> ProtoDefinitions { get; set; } = [];
/// <summary>
/// Gets or sets a value indicating whether OpenTelemetry tracing is enabled.
/// When enabled, WireMock.Net will emit distributed traces for request processing.
/// Default value is <c>false</c>.
/// </summary>
public bool OpenTelemetryEnabled { get; set; }
/// <summary>
/// Gets or sets the OTLP exporter endpoint URL.
/// When set, traces will be exported to this endpoint using the OTLP protocol.
/// Example: "http://localhost:4317" for gRPC or "http://localhost:4318" for HTTP.
/// If not set, the OTLP exporter will use the <c>OTEL_EXPORTER_OTLP_ENDPOINT</c> environment variable,
/// or fall back to the default endpoint (<c>http://localhost:4317</c> for gRPC).
/// </summary>
public string? OpenTelemetryOtlpExporterEndpoint { get; set; }
/// <summary>
/// Add an additional Urls on which WireMock should listen.
/// </summary>
/// <param name="additionalUrls">The additional urls which the WireMock Server should listen on.</param>
public void WithAdditionalUrls(params string[] additionalUrls)
{
foreach (var url in additionalUrls)
{
if (!PortUtils.TryExtract(Guard.NotNullOrEmpty(url), out _, out _, out _, out _, out var port))
{
throw new ArgumentException($"The URL '{url}' is not valid.");
}
AdditionalUrls.Add(Guard.NotNullOrWhiteSpace(url));
HttpPorts.Add(port);
}
}
/// <summary>
/// Add a Grpc ProtoDefinition at server-level.
/// </summary>
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
/// <param name="protoDefinitions">The ProtoDefinition as text.</param>
public void WithProtoDefinition(string id, params string[] protoDefinitions)
{
Guard.NotNullOrWhiteSpace(id);
Guard.NotNullOrEmpty(protoDefinitions);
ProtoDefinitions[id] = protoDefinitions;
}
/// <summary>
/// Converts the current instance's properties to an array of command-line arguments for starting the WireMock.Net server.
/// </summary>
@@ -95,6 +154,25 @@ public class WireMockServerArguments
Add(args, "--WatchStaticMappingsInSubdirectories", "true");
}
if (OpenTelemetryEnabled)
{
// Enable activity tracing (creates System.Diagnostics.Activity objects)
Add(args, "--ActivityTracingEnabled", "true");
// Enable OpenTelemetry exporter
Add(args, "--OpenTelemetryEnabled", "true");
if (!string.IsNullOrEmpty(OpenTelemetryOtlpExporterEndpoint))
{
Add(args, "--OpenTelemetryOtlpExporterEndpoint", OpenTelemetryOtlpExporterEndpoint);
}
}
if (AdditionalUrls.Count > 0)
{
Add(args, "--Urls", $"http://*:{HttpContainerPort} {string.Join(' ', AdditionalUrls)}");
}
return args
.SelectMany(k => new[] { k.Key, k.Value })
.ToArray();

View File

@@ -3,10 +3,12 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.WireMock;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Stef.Validation;
using WireMock.Client.Builders;
using WireMock.Net.Aspire;
using WireMock.Util;
// ReSharper disable once CheckNamespace
namespace Aspire.Hosting;
@@ -33,9 +35,31 @@ public static class WireMockServerBuilderExtensions
Guard.NotNullOrWhiteSpace(name);
Guard.Condition(port, p => p is null or > 0 and <= ushort.MaxValue);
return builder.AddWireMock(name, callback =>
return builder.AddWireMock(name, serverArguments =>
{
callback.HttpPort = port;
if (port != null)
{
serverArguments.HttpPorts = [port.Value];
}
});
}
/// <summary>
/// Adds a WireMock.Net Server resource to the application model.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="additionalUrls">The additional urls which the WireMock Server should listen on.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
public static IResourceBuilder<WireMockServerResource> AddWireMock(this IDistributedApplicationBuilder builder, string name, params string[] additionalUrls)
{
Guard.NotNull(builder);
Guard.NotNullOrWhiteSpace(name);
Guard.NotNull(additionalUrls);
return builder.AddWireMock(name, serverArguments =>
{
serverArguments.WithAdditionalUrls(additionalUrls);
});
}
@@ -53,13 +77,50 @@ public static class WireMockServerBuilderExtensions
Guard.NotNull(arguments);
var wireMockContainerResource = new WireMockServerResource(name, arguments);
var healthCheckKey = $"{name}_check";
var healthCheckRegistration = new HealthCheckRegistration(
healthCheckKey,
_ => new WireMockHealthCheck(wireMockContainerResource),
failureStatus: null,
tags: null);
builder.Services.AddHealthChecks().Add(healthCheckRegistration);
var resourceBuilder = builder
.AddResource(wireMockContainerResource)
.WithImage(DefaultLinuxImage)
.WithEnvironment(ctx => ctx.EnvironmentVariables.Add("DOTNET_USE_POLLING_FILE_WATCHER", "1")) // https://khalidabuhakmeh.com/aspnet-docker-gotchas-and-workarounds#configuration-reloads-and-filesystemwatcher
.WithHttpEndpoint(port: arguments.HttpPort, targetPort: WireMockServerArguments.HttpContainerPort)
.WithHealthCheck(healthCheckKey)
.WithWireMockInspectorCommand();
if (arguments.HttpPorts.Count == 0)
{
resourceBuilder = resourceBuilder.WithHttpEndpoint(port: null, targetPort: WireMockServerArguments.HttpContainerPort);
}
else if (arguments.HttpPorts.Count == 1)
{
resourceBuilder = resourceBuilder.WithHttpEndpoint(port: arguments.HttpPorts[0], targetPort: WireMockServerArguments.HttpContainerPort);
}
else
{
// Required for the default admin endpoint and health checks
resourceBuilder = resourceBuilder.WithHttpEndpoint(port: null, targetPort: WireMockServerArguments.HttpContainerPort);
var anyIsHttp2 = false;
foreach (var url in arguments.AdditionalUrls)
{
PortUtils.TryExtract(url, out _, out var isHttp2, out var scheme, out _, out var httpPort);
anyIsHttp2 |= isHttp2;
resourceBuilder = resourceBuilder.WithEndpoint(port: httpPort, targetPort: httpPort, scheme: scheme, name: $"{scheme}-{httpPort}");
}
if (anyIsHttp2)
{
resourceBuilder = resourceBuilder.AsHttp2Service();
}
}
if (!string.IsNullOrEmpty(arguments.MappingsPath))
{
resourceBuilder = resourceBuilder.WithBindMount(arguments.MappingsPath, DefaultLinuxMappingsPath);
@@ -73,6 +134,9 @@ public static class WireMockServerBuilderExtensions
}
});
// Always add the lifecycle hook to support dynamic mappings and proto definitions
resourceBuilder.ApplicationBuilder.Services.TryAddLifecycleHook<WireMockServerLifecycleHook>();
return resourceBuilder;
}
@@ -83,7 +147,10 @@ public static class WireMockServerBuilderExtensions
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="callback">A callback that allows for setting the <see cref="WireMockServerArguments"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
public static IResourceBuilder<WireMockServerResource> AddWireMock(this IDistributedApplicationBuilder builder, string name, Action<WireMockServerArguments> callback)
public static IResourceBuilder<WireMockServerResource> AddWireMock(
this IDistributedApplicationBuilder builder,
string name,
Action<WireMockServerArguments> callback)
{
Guard.NotNull(builder);
Guard.NotNullOrWhiteSpace(name);
@@ -154,7 +221,7 @@ public static class WireMockServerBuilderExtensions
/// </summary>
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</param>
/// <returns></returns>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, Task> configure)
{
return wiremock.WithApiMappingBuilder((adminApiMappingBuilder, _) => configure.Invoke(adminApiMappingBuilder));
@@ -165,13 +232,27 @@ public static class WireMockServerBuilderExtensions
/// </summary>
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</param>
/// <returns></returns>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, CancellationToken, Task> configure)
{
Guard.NotNull(wiremock);
wiremock.ApplicationBuilder.Services.TryAddLifecycleHook<WireMockServerLifecycleHook>();
wiremock.Resource.Arguments.ApiMappingBuilder = configure;
wiremock.Resource.ApiMappingState = WireMockMappingState.NotSubmitted;
return wiremock;
}
/// <summary>
/// Add a Grpc ProtoDefinition at server-level.
/// </summary>
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
/// <param name="id">Unique identifier for the ProtoDefinition.</param>
/// <param name="protoDefinitions">The ProtoDefinition as text.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
public static IResourceBuilder<WireMockServerResource> WithProtoDefinition(this IResourceBuilder<WireMockServerResource> wiremock, string id, params string[] protoDefinitions)
{
Guard.NotNull(wiremock).Resource.Arguments.WithProtoDefinition(id, protoDefinitions);
return wiremock;
}
@@ -183,11 +264,11 @@ public static class WireMockServerBuilderExtensions
/// dotnet tool install WireMockInspector --global --no-cache --ignore-failed-sources
/// </code>
/// </summary>
/// <param name="builder">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param>
/// <returns></returns>
public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> builder)
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> wiremock)
{
Guard.NotNull(builder);
Guard.NotNull(wiremock);
CommandOptions commandOptions = new()
{
@@ -197,13 +278,47 @@ public static class WireMockServerBuilderExtensions
IconVariant = IconVariant.Filled
};
builder.WithCommand(
wiremock.WithCommand(
name: "wiremock-inspector",
displayName: "WireMock Inspector",
executeCommand: _ => OnRunOpenInspectorCommandAsync(builder),
executeCommand: _ => OnRunOpenInspectorCommandAsync(wiremock),
commandOptions: commandOptions);
return builder;
return wiremock;
}
/// <summary>
/// Configures OpenTelemetry distributed tracing for the WireMock.Net server.
/// This enables automatic trace export to the Aspire dashboard.
/// </summary>
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
/// <remarks>
/// When enabled, WireMock.Net will emit distributed traces for each request processed,
/// including information about:
/// <list type="bullet">
/// <item>HTTP method, URL, and status code</item>
/// <item>Mapping match results and scores</item>
/// <item>Request processing duration</item>
/// </list>
/// The traces will automatically appear in the Aspire dashboard.
/// </remarks>
public static IResourceBuilder<WireMockServerResource> WithOpenTelemetry(this IResourceBuilder<WireMockServerResource> wiremock)
{
Guard.NotNull(wiremock);
// Enable OpenTelemetry in WireMock server arguments
wiremock.Resource.Arguments.OpenTelemetryEnabled = true;
// Use Aspire's standard WithOtlpExporter to configure OTEL environment variables for the container
// This sets OTEL_EXPORTER_OTLP_ENDPOINT which the OTLP exporter reads automatically
var containerBuilder = wiremock as IResourceBuilder<ContainerResource>;
if (containerBuilder != null)
{
containerBuilder.WithOtlpExporter();
}
return wiremock;
}
private static Task<ExecuteCommandResult> OnRunOpenInspectorCommandAsync(IResourceBuilder<WireMockServerResource> builder)

View File

@@ -1,5 +1,6 @@
// Copyright © WireMock.Net
using System.Diagnostics;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Logging;
@@ -10,32 +11,49 @@ internal class WireMockServerLifecycleHook(ILoggerFactory loggerFactory) : IDist
{
private readonly CancellationTokenSource _shutdownCts = new();
public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
private CancellationTokenSource? _linkedCts;
private Task? _mappingTask;
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
var wireMockServerResources = appModel.Resources
.OfType<WireMockServerResource>()
.ToArray();
foreach (var wireMockServerResource in wireMockServerResources)
_mappingTask = Task.Run(async () =>
{
wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>());
var wireMockServerResources = appModel.Resources
.OfType<WireMockServerResource>()
.ToArray();
var endpoint = wireMockServerResource.GetEndpoint();
if (endpoint.IsAllocated)
foreach (var wireMockServerResource in wireMockServerResources)
{
await wireMockServerResource.WaitForHealthAsync(cts.Token);
wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>());
await wireMockServerResource.CallApiMappingBuilderActionAsync(cts.Token);
var endpoint = wireMockServerResource.GetEndpoint();
Debug.Assert(endpoint.IsAllocated);
wireMockServerResource.StartWatchingStaticMappings(cts.Token);
await wireMockServerResource.WaitForHealthAsync(_linkedCts.Token);
await wireMockServerResource.CallAddProtoDefinitionsAsync(_linkedCts.Token);
await wireMockServerResource.CallApiMappingBuilderActionAsync(_linkedCts.Token);
wireMockServerResource.StartWatchingStaticMappings(_linkedCts.Token);
}
}
}, _linkedCts.Token);
return Task.CompletedTask;
}
public async ValueTask DisposeAsync()
{
await _shutdownCts.CancelAsync();
_linkedCts?.Dispose();
_shutdownCts.Dispose();
if (_mappingTask is not null)
{
await _mappingTask;
}
}
}

View File

@@ -5,6 +5,7 @@ using RestEase;
using Stef.Validation;
using WireMock.Client;
using WireMock.Client.Extensions;
using WireMock.Net.Aspire;
using WireMock.Util;
// ReSharper disable once CheckNamespace
@@ -19,6 +20,7 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
internal WireMockServerArguments Arguments { get; }
internal Lazy<IWireMockAdminApi> AdminApi => new(CreateWireMockAdminApi);
internal WireMockMappingState ApiMappingState { get; set; } = WireMockMappingState.NoMappings;
private ILogger? _logger;
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
@@ -64,6 +66,36 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
var mappingBuilder = AdminApi.Value.GetMappingBuilder();
await Arguments.ApiMappingBuilder.Invoke(mappingBuilder, cancellationToken);
ApiMappingState = WireMockMappingState.Submitted;
}
internal async Task CallAddProtoDefinitionsAsync(CancellationToken cancellationToken)
{
_logger?.LogInformation("Calling AdminApi to add GRPC ProtoDefinition at server level to WireMock.Net");
foreach (var (id, protoDefinitions) in Arguments.ProtoDefinitions)
{
_logger?.LogInformation("Adding ProtoDefinition {Id}", id);
foreach (var protoDefinition in protoDefinitions)
{
try
{
var status = await AdminApi.Value.AddProtoDefinitionAsync(id, protoDefinition, cancellationToken);
_logger?.LogInformation("ProtoDefinition '{Id}' added with status: {Status}.", id, status.Status);
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", id);
}
}
}
// Force a reload of static mappings when ProtoDefinitions are added at server-level to fix #1382
if (Arguments.ProtoDefinitions.Count > 0)
{
await ReloadStaticMappingsAsync(default);
}
}
internal void StartWatchingStaticMappings(CancellationToken cancellationToken)
@@ -109,10 +141,17 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args)
{
_logger?.LogInformation("MappingFile created, changed or deleted: '{0}'. Triggering ReloadStaticMappings.", args.FullPath);
_logger?.LogInformation("MappingFile created, changed or deleted: '{FullPath}'. Triggering ReloadStaticMappings.", args.FullPath);
await ReloadStaticMappingsAsync(default);
}
private async Task ReloadStaticMappingsAsync(CancellationToken cancellationToken)
{
try
{
await AdminApi.Value.ReloadStaticMappingsAsync();
var status = await AdminApi.Value.ReloadStaticMappingsAsync(cancellationToken);
_logger?.LogInformation("ReloadStaticMappings called with status: {Status}.", status);
}
catch (Exception ex)
{

View File

@@ -140,7 +140,7 @@ public class GraphQLMatcher : IGraphQLMatcher
}
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />

View File

@@ -97,7 +97,7 @@ public class CSharpCodeMatcher : ICSharpCodeMatcher
}
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
private bool IsMatch(dynamic input, string pattern)

View File

@@ -1,6 +1,7 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using WireMock.Matchers.Helpers;
using WireMock.Models.Mime;
using WireMock.Util;
@@ -12,7 +13,7 @@ namespace WireMock.Matchers;
/// </summary>
public class MimePartMatcher : IMimePartMatcher
{
private readonly Func<IMimePartData, MatchResult>[] _funcs;
private readonly IList<(string Name, Func<IMimePartData, MatchResult> func)> _matcherFunctions;
/// <inheritdoc />
public string Name => nameof(MimePartMatcher);
@@ -49,34 +50,47 @@ public class MimePartMatcher : IMimePartMatcher
ContentTransferEncodingMatcher = contentTransferEncodingMatcher;
ContentMatcher = contentMatcher;
_funcs =
[
mp => ContentTypeMatcher?.IsMatch(GetContentTypeAsString(mp.ContentType)) ?? MatchScores.Perfect,
mp => ContentDispositionMatcher?.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty)) ?? MatchScores.Perfect,
mp => ContentTransferEncodingMatcher?.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant()) ?? MatchScores.Perfect,
MatchOnContent
];
_matcherFunctions = [];
if (ContentTypeMatcher != null)
{
_matcherFunctions.Add((nameof(ContentTypeMatcher), mp => ContentTypeMatcher.IsMatch(GetContentTypeAsString(mp.ContentType))));
}
if (ContentDispositionMatcher != null)
{
_matcherFunctions.Add((nameof(ContentDispositionMatcher), mp => ContentDispositionMatcher.IsMatch(mp.ContentDisposition?.ToString()?.Replace("Content-Disposition: ", string.Empty))));
}
if (ContentTransferEncodingMatcher != null)
{
_matcherFunctions.Add((nameof(ContentTransferEncodingMatcher), mp => ContentTransferEncodingMatcher.IsMatch(mp.ContentTransferEncoding.ToLowerInvariant())));
}
if (ContentMatcher != null)
{
_matcherFunctions.Add((ContentMatcher.Name, MatchOnContent));
}
}
/// <inheritdoc />
public MatchResult IsMatch(IMimePartData value)
{
var score = MatchScores.Mismatch;
Exception? exception = null;
var results = new List<MatchResult>();
try
foreach (var matcherFunction in _matcherFunctions)
{
if (Array.TrueForAll(_funcs, func => func(value).IsPerfect()))
try
{
score = MatchScores.Perfect;
var matchResult = matcherFunction.func(value);
results.Add(MatchResult.From(matcherFunction.Name, matchResult.Score));
}
catch (Exception ex)
{
results.Add(MatchResult.From(matcherFunction.Name, MatchScores.Mismatch, ex));
}
}
catch (Exception ex)
{
exception = ex;
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(nameof(MimePartMatcher), results, MatchOperator.And);
}
/// <inheritdoc />
@@ -87,11 +101,6 @@ public class MimePartMatcher : IMimePartMatcher
private MatchResult MatchOnContent(IMimePartData mimePart)
{
if (ContentMatcher == null)
{
return MatchScores.Perfect;
}
var bodyParserSettings = new BodyParserSettings
{
Stream = mimePart.Open(),
@@ -102,7 +111,7 @@ public class MimePartMatcher : IMimePartMatcher
};
var bodyData = BodyParser.ParseAsync(bodyParserSettings).ConfigureAwait(false).GetAwaiter().GetResult();
return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher);
return BodyDataMatchScoreCalculator.CalculateMatchScore(bodyData, ContentMatcher!);
}
private static string? GetContentTypeAsString(IContentTypeData? contentType)

View File

@@ -1,7 +1,6 @@
// Copyright © WireMock.Net
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -33,18 +32,27 @@ internal class MimeKitUtils : IMimeKitUtils
StartsWithMultiPart(contentTypeHeader)
)
{
var bytes = requestMessage.BodyData?.DetectedBodyType switch
byte[] bytes;
switch (requestMessage.BodyData?.DetectedBodyType)
{
// If the body is bytes, use the BodyAsBytes to match on.
BodyType.Bytes => requestMessage.BodyData.BodyAsBytes!,
case BodyType.Bytes:
bytes = requestMessage.BodyData.BodyAsBytes!;
break;
// If the body is a String or MultiPart, use the BodyAsString to match on.
BodyType.String or BodyType.MultiPart => Encoding.UTF8.GetBytes(requestMessage.BodyData.BodyAsString!),
case BodyType.String or BodyType.MultiPart:
bytes = Encoding.UTF8.GetBytes(requestMessage.BodyData.BodyAsString!);
break;
_ => throw new NotSupportedException()
};
// Else return false.
default:
mimeMessageData = null;
return false;
}
var fixedBytes = FixBytes(bytes, contentTypeHeader[0]);
var fixedBytes = PrependContentTypeHeader(bytes, contentTypeHeader[0]);
mimeMessageData = LoadFromStream(new MemoryStream(fixedBytes));
return true;
@@ -60,7 +68,10 @@ internal class MimeKitUtils : IMimeKitUtils
return contentTypeHeader.Any(ct => ct.TrimStart().StartsWith("multipart/", StringComparison.OrdinalIgnoreCase));
}
private static byte[] FixBytes(byte[] bytes, WireMockList<string> contentType)
/// <summary>
/// Prepends the Content-Type header to the byte array to make it a valid MIME message for MimeKit.
/// </summary>
private static byte[] PrependContentTypeHeader(byte[] bytes, WireMockList<string> contentType)
{
var contentTypeBytes = Encoding.UTF8.GetBytes($"{HttpKnownHeaderNames.ContentType}: {contentType}\r\n\r\n");

View File

@@ -54,7 +54,7 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
{
if (string.IsNullOrEmpty(input))
{
return MatchScores.Mismatch;
return MatchResult.From(Name);
}
var token = Regex.Replace(input, BearerPrefix, string.Empty, RegexOptions.IgnoreCase, RegexConstants.DefaultTimeout);
@@ -83,11 +83,11 @@ internal class AzureADAuthenticationMatcher : IStringMatcher
// Throws an Exception as the token is invalid (expired, invalid-formatted, tenant mismatch, etc.)
_jwtSecurityTokenHandler.ValidateToken(token, validationParameters, out _);
return MatchScores.Perfect;
return MatchResult.From(Name, MatchScores.Perfect);
}
catch (Exception ex)
{
return new MatchResult(MatchScores.Mismatch, ex);
return MatchResult.From(Name, ex);
}
}

View File

@@ -15,8 +15,13 @@ internal static class HttpClientBuilder
var handler = new HttpClientHandler
{
CheckCertificateRevocationList = false,
#if NET5_0_OR_GREATER
SslProtocols = System.Security.Authentication.SslProtocols.Tls13 | System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
#else
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
#endif
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
#elif NET46
@@ -62,7 +67,10 @@ internal static class HttpClientBuilder
}
}
#if !NETSTANDARD1_3
#if NET5_0_OR_GREATER
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
#elif !NETSTANDARD1_3
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, errors) => true;
#endif

View File

@@ -11,24 +11,17 @@ using Stef.Validation;
using WireMock.Models;
using WireMock.Settings;
using WireMock.Transformers;
using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban;
using WireMock.Types;
using WireMock.Util;
namespace WireMock.Http;
internal class WebhookSender
internal class WebhookSender(WireMockServerSettings settings)
{
private const string ClientIp = "::1";
private static readonly ThreadLocal<Random> Random = new(() => new Random(DateTime.UtcNow.Millisecond));
private readonly WireMockServerSettings _settings;
public WebhookSender(WireMockServerSettings settings)
{
_settings = Guard.NotNull(settings);
}
private readonly WireMockServerSettings _settings = Guard.NotNull(settings);
public async Task<HttpResponseMessage> SendAsync(
HttpClient client,
@@ -49,24 +42,7 @@ internal class WebhookSender
string requestUrl;
if (webhookRequest.UseTransformer == true)
{
ITransformer transformer;
switch (webhookRequest.TransformerType)
{
case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(_settings);
transformer = new Transformer(_settings, factoryHandlebars);
break;
case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(_settings.FileSystemHandler, webhookRequest.TransformerType);
transformer = new Transformer(_settings, factoryDotLiquid);
break;
default:
throw new NotImplementedException($"TransformerType '{webhookRequest.TransformerType}' is not supported.");
}
var transformer = TransformerFactory.Create(webhookRequest.TransformerType, _settings);
bodyData = transformer.TransformBody(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.BodyData, webhookRequest.TransformerReplaceNodeOptions);
headers = transformer.TransformHeaders(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Headers);
requestUrl = transformer.TransformString(mapping, originalRequestMessage, originalResponseMessage, webhookRequest.Url);

View File

@@ -69,7 +69,7 @@ public class WireMockConsoleLogger : IWireMockLogger
/// <see cref="IWireMockLogger.DebugRequestResponse"/>
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
string message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
Console.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
}

View File

@@ -0,0 +1,46 @@
// Copyright © WireMock.Net
using System;
namespace WireMock.Matchers;
/// <summary>
/// Represents a matcher that combines multiple matching strategies into a single composite operation.
/// </summary>
public class CompositeMatcher : IMatcher
{
/// <inheritdoc />
public string Name => nameof(CompositeMatcher);
/// <summary>
/// The logical operator used to combine the results of the matchers.
/// </summary>
public MatchOperator MatchOperator { get; }
/// <inheritdoc />
public MatchBehaviour MatchBehaviour { get; }
/// <summary>
/// All matchers.
/// </summary>
public IMatcher[] Matchers { get; }
/// <summary>
/// Initializes a new instance of the CompositeMatcher class with the specified matchers, operator, and match behaviour.
/// </summary>
/// <param name="matchers">An array of matchers to be combined. Cannot be null or contain null elements.</param>
/// <param name="matchOperator">The logical operator used to combine the results of the matchers.</param>
/// <param name="matchBehaviour">The behaviour that determines how the composite matcher interprets the combined results.</param>
public CompositeMatcher(IMatcher[] matchers, MatchOperator matchOperator, MatchBehaviour matchBehaviour)
{
Matchers = matchers;
MatchOperator = matchOperator;
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc />
public string GetCSharpCodeArguments()
{
throw new NotImplementedException();
}
}

View File

@@ -62,7 +62,7 @@ public class ContentTypeMatcher : WildcardMatcher
{
if (string.IsNullOrEmpty(input) || !MediaTypeHeaderValue.TryParse(input, out var contentType))
{
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
}
return base.IsMatch(contentType.MediaType);

View File

@@ -75,7 +75,7 @@ public class ExactMatcher : IStringMatcher, IIgnoreCaseMatcher
: pattern => pattern == input;
var score = MatchScores.ToScore(_values.Select(v => equals(v)).ToArray(), MatchOperator);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
}
/// <inheritdoc />

View File

@@ -106,18 +106,18 @@ public class FormUrlEncodedMatcher : IStringMatcher, IIgnoreCaseMatcher
// Input is null or empty and if no patterns defined, return Perfect match.
if (string.IsNullOrEmpty(input) && _patterns.Length == 0)
{
return new MatchResult(MatchScores.Perfect);
return MatchResult.From(Name, MatchScores.Perfect);
}
if (!QueryStringParser.TryParse(input, IgnoreCase, out var inputNameValueCollection))
{
return new MatchResult(MatchScores.Mismatch);
return MatchResult.From(Name, MatchScores.Mismatch);
}
var matches = GetMatches(inputNameValueCollection);
var score = MatchScores.ToScore(matches, MatchOperator);
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score));
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
}
private bool[] GetMatches(IDictionary<string, string> inputNameValueCollection)

View File

@@ -80,7 +80,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
}
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />
@@ -104,7 +104,7 @@ public class JsonPathMatcher : IStringMatcher, IObjectMatcher
}
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />

View File

@@ -87,7 +87,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
}
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
/// <inheritdoc />
@@ -102,7 +102,7 @@ public class JmesPathMatcher : IStringMatcher, IObjectMatcher
return IsMatch(inputAsString);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
}
/// <inheritdoc />

View File

@@ -96,7 +96,7 @@ public class JsonMatcher : IJsonMatcher
}
}
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), error);
}
/// <inheritdoc />

View File

@@ -24,14 +24,26 @@ public class RequestMatchResult : IRequestMatchResult
public double AverageTotalScore => TotalNumber == 0 ? MatchScores.Mismatch : TotalScore / TotalNumber;
/// <inheritdoc />
public IList<MatchDetail> MatchDetails { get; } = new List<MatchDetail>();
public IList<MatchDetail> MatchDetails { get; } = [];
/// <inheritdoc />
public double AddScore(Type matcherType, double score, Exception? exception)
{
MatchDetails.Add(new MatchDetail { MatcherType = matcherType, Score = score, Exception = exception });
return AddMatchDetail(new MatchDetail
{
Name = matcherType.Name.Replace("RequestMessage", string.Empty),
MatcherType = matcherType,
Score = score,
Exception = exception
});
}
return score;
/// <inheritdoc />
public double AddMatchDetail(MatchDetail matchDetail)
{
MatchDetails.Add(matchDetail);
return matchDetail.Score;
}
/// <summary>

View File

@@ -17,27 +17,27 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <summary>
/// The body function
/// </summary>
public Func<string?, bool>? Func { get; }
public Func<string?, bool>? MatchOnBodyAsStringFunc { get; }
/// <summary>
/// The body data function for byte[]
/// </summary>
public Func<byte[]?, bool>? DataFunc { get; }
public Func<byte[]?, bool>? MatchOnBodyAsBytesFunc { get; }
/// <summary>
/// The body data function for json
/// </summary>
public Func<object?, bool>? JsonFunc { get; }
public Func<object?, bool>? MatchOnBodyAsJsonFunc { get; }
/// <summary>
/// The body data function for BodyData
/// </summary>
public Func<IBodyData?, bool>? BodyDataFunc { get; }
public Func<IBodyData?, bool>? MatchOnBodyAsBodyDataFunc { get; }
/// <summary>
/// The body data function for FormUrlEncoded
/// </summary>
public Func<IDictionary<string, string>?, bool>? FormUrlEncodedFunc { get; }
public Func<IDictionary<string, string>?, bool>? MatchOnBodyAsFormUrlEncodedFunc { get; }
/// <summary>
/// The matchers.
@@ -85,7 +85,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<string?, bool> func)
{
Func = Guard.NotNull(func);
MatchOnBodyAsStringFunc = Guard.NotNull(func);
}
/// <summary>
@@ -94,7 +94,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<byte[]?, bool> func)
{
DataFunc = Guard.NotNull(func);
MatchOnBodyAsBytesFunc = Guard.NotNull(func);
}
/// <summary>
@@ -103,7 +103,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<object?, bool> func)
{
JsonFunc = Guard.NotNull(func);
MatchOnBodyAsJsonFunc = Guard.NotNull(func);
}
/// <summary>
@@ -112,7 +112,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IBodyData?, bool> func)
{
BodyDataFunc = Guard.NotNull(func);
MatchOnBodyAsBodyDataFunc = Guard.NotNull(func);
}
/// <summary>
@@ -121,7 +121,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<IDictionary<string, string>?, bool> func)
{
FormUrlEncodedFunc = Guard.NotNull(func);
MatchOnBodyAsFormUrlEncodedFunc = Guard.NotNull(func);
}
/// <summary>
@@ -147,43 +147,43 @@ public class RequestMessageBodyMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = CalculateMatchScore(requestMessage).Expand();
var (score, exception) = CalculateMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private MatchResult CalculateMatchScore(IRequestMessage requestMessage)
private MatchResult CalculateMatchResult(IRequestMessage requestMessage)
{
if (Matchers != null && Matchers.Any())
{
var results = Matchers.Select(matcher => BodyDataMatchScoreCalculator.CalculateMatchScore(requestMessage.BodyData, matcher)).ToArray();
return MatchResult.From(results, MatchOperator);
return MatchResult.From(nameof(RequestMessageBodyMatcher), results, MatchOperator);
}
if (Func != null)
if (MatchOnBodyAsStringFunc != null)
{
return MatchScores.ToScore(Func(requestMessage.BodyData?.BodyAsString));
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsStringFunc)}", MatchScores.ToScore(MatchOnBodyAsStringFunc(requestMessage.BodyData?.BodyAsString)));
}
if (FormUrlEncodedFunc != null)
if (MatchOnBodyAsFormUrlEncodedFunc != null)
{
return MatchScores.ToScore(FormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded));
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsFormUrlEncodedFunc)}", MatchScores.ToScore(MatchOnBodyAsFormUrlEncodedFunc(requestMessage.BodyData?.BodyAsFormUrlEncoded)));
}
if (JsonFunc != null)
if (MatchOnBodyAsJsonFunc != null)
{
return MatchScores.ToScore(JsonFunc(requestMessage.BodyData?.BodyAsJson));
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsJsonFunc)}", MatchScores.ToScore(MatchOnBodyAsJsonFunc(requestMessage.BodyData?.BodyAsJson)));
}
if (DataFunc != null)
if (MatchOnBodyAsBytesFunc != null)
{
return MatchScores.ToScore(DataFunc(requestMessage.BodyData?.BodyAsBytes));
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsBytesFunc)}", MatchScores.ToScore(MatchOnBodyAsBytesFunc(requestMessage.BodyData?.BodyAsBytes)));
}
if (BodyDataFunc != null)
if (MatchOnBodyAsBodyDataFunc != null)
{
return MatchScores.ToScore(BodyDataFunc(requestMessage.BodyData));
return MatchResult.From($"{nameof(RequestMessageBodyMatcher)}:{nameof(MatchOnBodyAsBodyDataFunc)}", MatchScores.ToScore(MatchOnBodyAsBodyDataFunc(requestMessage.BodyData)));
}
return default;
return MatchResult.From(nameof(RequestMessageBodyMatcher));
}
}

View File

@@ -0,0 +1,62 @@
// Copyright © WireMock.Net
using System;
using Newtonsoft.Json.Linq;
using Stef.Validation;
namespace WireMock.Matchers.Request;
/// <summary>
/// The request body matcher.
/// </summary>
public class RequestMessageBodyMatcher<T> : IRequestMatcher
{
private const string _name = nameof(RequestMessageBodyMatcher<T>);
/// <summary>
/// The body data function for type T
/// </summary>
public Func<T?, bool>? Func { get; }
/// <summary>
/// The <see cref="MatchOperator"/>
/// </summary>
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
/// <summary>
/// Initializes a new instance of the <see cref="RequestMessageBodyMatcher"/> class.
/// </summary>
/// <param name="func">The function.</param>
public RequestMessageBodyMatcher(Func<T?, bool> func)
{
Func = Guard.NotNull(func);
}
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = CalculateMatchScore(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
}
private MatchResult CalculateMatchScore(IRequestMessage requestMessage)
{
if (Func != null)
{
if (requestMessage.BodyData?.BodyAsJson is JObject jsonObject)
{
try
{
var bodyAsT = jsonObject.ToObject<T>();
return MatchResult.From(_name, MatchScores.ToScore(Func(bodyAsT)));
}
catch (Exception ex)
{
return MatchResult.From(_name, ex);
}
}
}
return MatchResult.From(_name);
}
}

View File

@@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request;
/// </summary>
public class RequestMessageClientIPMatcher : IRequestMatcher
{
private const string _name = nameof(RequestMessageClientIPMatcher);
/// <summary>
/// The matchers
/// </summary>
@@ -77,8 +79,8 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
var matchDetail = GetMatchResult(requestMessage).ToMatchDetail();
return requestMatchResult.AddMatchDetail(matchDetail);
}
private MatchResult GetMatchResult(IRequestMessage requestMessage)
@@ -86,15 +88,15 @@ public class RequestMessageClientIPMatcher : IRequestMatcher
if (Matchers != null)
{
var results = Matchers.Select(m => m.IsMatch(requestMessage.ClientIP)).ToArray();
return MatchResult.From(results, MatchOperator);
return MatchResult.From(_name, results, MatchOperator);
}
if (Funcs != null)
{
var results = Funcs.Select(func => func(requestMessage.ClientIP)).ToArray();
return MatchScores.ToScore(results, MatchOperator);
return MatchResult.From(_name, MatchScores.ToScore(results, MatchOperator));
}
return default;
return MatchResult.From(_name);
}
}

View File

@@ -13,6 +13,8 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageCookieMatcher : IRequestMatcher
{
private const string _name = nameof(RequestMessageCookieMatcher);
/// <summary>
/// MatchBehaviour
/// </summary>
@@ -104,7 +106,7 @@ public class RequestMessageCookieMatcher : IRequestMatcher
{
if (requestMessage.Cookies == null)
{
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
}
// Check if we want to use IgnoreCase to compare the Cookie-Name and Cookie-Value
@@ -112,19 +114,19 @@ public class RequestMessageCookieMatcher : IRequestMatcher
if (Funcs != null)
{
return MatchScores.ToScore(Funcs.Any(f => f(cookies)));
return MatchResult.From(_name, MatchScores.ToScore(Funcs.Any(f => f(cookies))));
}
if (Matchers == null)
{
return default;
return MatchResult.From(_name);
}
if (!cookies.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
}
return Matchers.Max(m => m.IsMatch(cookies[Name]));
return MatchResult.From(_name, Matchers.Max(m => m.IsMatch(cookies[Name]))?.Score ?? MatchScores.Mismatch);
}
}

View File

@@ -14,6 +14,8 @@ namespace WireMock.Matchers.Request;
/// <inheritdoc cref="IRequestMatcher"/>
public class RequestMessageHeaderMatcher : IRequestMatcher
{
private const string _name = nameof(RequestMessageCookieMatcher);
/// <summary>
/// MatchBehaviour
/// </summary>
@@ -117,7 +119,7 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{
if (requestMessage.Headers == null)
{
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
}
// Check if we want to use IgnoreCase to compare the Header-Name and Header-Value(s)
@@ -126,14 +128,14 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
if (Funcs != null)
{
var funcResults = Funcs.Select(f => f(headers.ToDictionary(entry => entry.Key, entry => entry.Value.ToArray()))).ToArray();
return MatchScores.ToScore(funcResults, MatchOperator);
return MatchResult.From(_name, MatchScores.ToScore(funcResults, MatchOperator));
}
if (Matchers != null)
{
if (!headers.ContainsKey(Name))
{
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
}
var results = new List<MatchResult>();
@@ -141,12 +143,12 @@ public class RequestMessageHeaderMatcher : IRequestMatcher
{
var resultsPerMatcher = headers[Name].Select(matcher.IsMatch).ToArray();
results.Add(MatchResult.From(resultsPerMatcher, MatchOperator.And));
results.Add(MatchResult.From(_name, resultsPerMatcher, MatchOperator.And));
}
return MatchResult.From(results, MatchOperator);
return MatchResult.From(_name, results, MatchOperator);
}
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch);
return MatchResult.From(_name, MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.Mismatch));
}
}

View File

@@ -11,6 +11,8 @@ namespace WireMock.Matchers.Request;
/// </summary>
public class RequestMessageHttpVersionMatcher : IRequestMatcher
{
private const string _name = nameof(RequestMessageHttpVersionMatcher);
/// <summary>
/// The matcher.
/// </summary>
@@ -19,7 +21,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
/// <summary>
/// The func.
/// </summary>
public Func<string, bool>? Func { get; }
public Func<string, bool>? MatcherOnStringFunc { get; }
/// <summary>
/// The <see cref="MatchBehaviour"/>
@@ -61,7 +63,7 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
/// <param name="func">The function.</param>
public RequestMessageHttpVersionMatcher(Func<string, bool> func)
{
Func = Guard.NotNull(func);
MatcherOnStringFunc = Guard.NotNull(func);
}
/// <inheritdoc />
@@ -78,11 +80,11 @@ public class RequestMessageHttpVersionMatcher : IRequestMatcher
return Matcher.IsMatch(requestMessage.HttpVersion);
}
if (Func != null)
if (MatcherOnStringFunc != null)
{
return MatchScores.ToScore(Func(requestMessage.HttpVersion));
return MatchResult.From($"{_name}:{nameof(MatcherOnStringFunc)}", MatchScores.ToScore(MatcherOnStringFunc(requestMessage.HttpVersion)));
}
return default;
return MatchResult.From(_name);
}
}

View File

@@ -12,7 +12,12 @@ namespace WireMock.Matchers.Request;
/// </summary>
public class RequestMessageMultiPartMatcher : IRequestMatcher
{
private static readonly IMimeKitUtils MimeKitUtils = TypeLoader.LoadStaticInstance<IMimeKitUtils>();
/// <summary>
/// The name of this matcher.
/// </summary>
public const string Name = "MultiPartMatcher";
private readonly IMimeKitUtils _mimeKitUtils = LoadMimeKitUtils();
/// <summary>
/// The matchers.
@@ -22,7 +27,7 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
/// <summary>
/// The <see cref="MatchOperator"/>
/// </summary>
public MatchOperator MatchOperator { get; } = MatchOperator.Or;
public MatchOperator MatchOperator { get; } = MatchOperator.And;
/// <summary>
/// The <see cref="MatchBehaviour"/>
@@ -54,19 +59,20 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var score = MatchScores.Mismatch;
var matchDetail = MatchResult.From(Name).ToMatchDetail();
Exception? exception = null;
if (Matchers?.Any() != true)
if (Matchers == null)
{
return requestMatchResult.AddScore(GetType(), score, null);
return requestMatchResult.AddMatchDetail(matchDetail);
}
if (!MimeKitUtils.TryGetMimeMessage(requestMessage, out var message))
if (!_mimeKitUtils.TryGetMimeMessage(requestMessage, out var message))
{
return requestMatchResult.AddScore(GetType(), score, null);
return requestMatchResult.AddMatchDetail(matchDetail);
}
double score = MatchScores.Mismatch;
try
{
foreach (var mimePartMatcher in Matchers.OfType<IMimePartMatcher>().ToArray())
@@ -94,6 +100,16 @@ public class RequestMessageMultiPartMatcher : IRequestMatcher
exception = ex;
}
return requestMatchResult.AddScore(GetType(), score, exception);
return requestMatchResult.AddMatchDetail(MatchResult.From(Name, score, exception).ToMatchDetail());
}
private static IMimeKitUtils LoadMimeKitUtils()
{
if (TypeLoader.TryLoadStaticInstance<IMimeKitUtils>(out var mimeKitUtils))
{
return mimeKitUtils;
}
throw new InvalidOperationException("MimeKit is required for RequestMessageMultiPartMatcher. Please install the WireMock.Net.MimePart package.");
}
}

View File

@@ -77,8 +77,8 @@ public class RequestMessagePathMatcher : IRequestMatcher
/// <inheritdoc />
public double GetMatchingScore(IRequestMessage requestMessage, IRequestMatchResult requestMatchResult)
{
var (score, exception) = GetMatchResult(requestMessage).Expand();
return requestMatchResult.AddScore(GetType(), score, exception);
var matchDetail = GetMatchResult(requestMessage).ToMatchDetail();
return requestMatchResult.AddMatchDetail(matchDetail);
}
private MatchResult GetMatchResult(IRequestMessage requestMessage)
@@ -86,15 +86,16 @@ public class RequestMessagePathMatcher : IRequestMatcher
if (Matchers != null)
{
var results = Matchers.Select(m => m.IsMatch(requestMessage.Path)).ToArray();
return MatchResult.From(results, MatchOperator);
return MatchResult.From(nameof(RequestMessagePathMatcher), results, MatchOperator);
}
if (Funcs != null)
{
var results = Funcs.Select(func => func(requestMessage.Path)).ToArray();
return MatchScores.ToScore(results, MatchOperator);
var score = MatchScores.ToScore(results, MatchOperator);
return MatchResult.From(nameof(RequestMessagePathMatcher), score);
}
return default;
return MatchResult.From(nameof(RequestMessagePathMatcher));
}
}

View File

@@ -86,15 +86,16 @@ public class RequestMessageUrlMatcher : IRequestMatcher
if (Matchers != null)
{
var results = Matchers.Select(m => m.IsMatch(requestMessage.Url)).ToArray();
return MatchResult.From(results, MatchOperator);
return MatchResult.From(nameof(RequestMessageUrlMatcher), results, MatchOperator);
}
if (Funcs != null)
{
var results = Funcs.Select(func => func(requestMessage.Url)).ToArray();
return MatchScores.ToScore(results, MatchOperator);
var score = MatchScores.ToScore(results, MatchOperator);
return MatchResult.From(nameof(RequestMessageUrlMatcher), score);
}
return default;
return MatchResult.From(nameof(RequestMessageUrlMatcher));
}
}

View File

@@ -86,7 +86,7 @@ public class SimMetricsMatcher : IStringMatcher
IStringMetric stringMetricType = GetStringMetricType();
var score = MatchScores.ToScore(_patterns.Select(p => stringMetricType.GetSimilarity(p.GetPattern(), input)).ToArray(), MatchOperator);
return MatchBehaviourHelper.Convert(MatchBehaviour, score);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score));
}
/// <inheritdoc />

View File

@@ -116,7 +116,7 @@ public class XPathMatcher : IStringMatcher
private MatchResult CreateMatchResult(double score, Exception? exception = null)
{
return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
return MatchResult.From(Name, MatchBehaviourHelper.Convert(MatchBehaviour, score), exception);
}
private sealed class XPathEvaluator

View File

@@ -0,0 +1,38 @@
// Copyright © WireMock.Net
#if ACTIVITY_TRACING_SUPPORTED
namespace WireMock.Owin.ActivityTracing;
/// <summary>
/// Options for controlling activity tracing in WireMock.Net middleware.
/// These options control the creation of System.Diagnostics.Activity objects
/// but do not require any OpenTelemetry exporter dependencies.
/// </summary>
public class ActivityTracingOptions
{
/// <summary>
/// Gets or sets a value indicating whether to exclude admin interface requests from tracing.
/// Default is <c>true</c>.
/// </summary>
public bool ExcludeAdminRequests { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether to record request body in trace attributes.
/// Default is <c>false</c> due to potential PII concerns.
/// </summary>
public bool RecordRequestBody { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to record response body in trace attributes.
/// Default is <c>false</c> due to potential PII concerns.
/// </summary>
public bool RecordResponseBody { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to record mapping match details in trace attributes.
/// Default is <c>true</c>.
/// </summary>
public bool RecordMatchDetails { get; set; } = true;
}
#endif

View File

@@ -0,0 +1,34 @@
// Copyright © WireMock.Net
#if !ACTIVITY_TRACING_SUPPORTED
using System;
#endif
using WireMock.Settings;
namespace WireMock.Owin.ActivityTracing;
/// <summary>
/// Validator for Activity Tracing configuration.
/// </summary>
internal static class ActivityTracingValidator
{
/// <summary>
/// Validates that Activity Tracing is supported on the current framework.
/// Throws an exception if ActivityTracingOptions is configured on an unsupported framework.
/// </summary>
/// <param name="settings">The WireMock server settings to validate.</param>
/// <exception cref="System.InvalidOperationException">
/// Thrown when ActivityTracingOptions is configured but the current framework does not support System.Diagnostics.Activity.
/// </exception>
public static void ValidateActivityApiPresence(WireMockServerSettings settings)
{
#if !ACTIVITY_TRACING_SUPPORTED
if (settings.ActivityTracingOptions is not null)
{
throw new InvalidOperationException(
"Activity Tracing is not supported on this target framework. " +
"It requires .NET 5.0 or higher which includes System.Diagnostics.Activity support.");
}
#endif
}
}

View File

@@ -0,0 +1,200 @@
// Copyright © WireMock.Net
#if ACTIVITY_TRACING_SUPPORTED
using System;
using System.Diagnostics;
using WireMock.Logging;
namespace WireMock.Owin.ActivityTracing;
/// <summary>
/// Provides an ActivitySource for WireMock.Net distributed tracing.
/// </summary>
public static class WireMockActivitySource
{
/// <summary>
/// The name of the ActivitySource used by WireMock.Net.
/// </summary>
internal const string SourceName = "WireMock.Net";
/// <summary>
/// The ActivitySource instance used for creating tracing activities.
/// </summary>
public static readonly ActivitySource Source = new(SourceName, GetVersion());
private static string GetVersion()
{
return typeof(WireMockActivitySource).Assembly.GetName().Version?.ToString() ?? "1.0.0";
}
/// <summary>
/// Starts a new activity for a WireMock request.
/// </summary>
/// <param name="requestMethod">The HTTP method of the request.</param>
/// <param name="requestPath">The path of the request.</param>
/// <returns>The started activity, or null if tracing is not enabled.</returns>
internal static Activity? StartRequestActivity(string requestMethod, string requestPath)
{
if (!Source.HasListeners())
{
return null;
}
var activity = Source.StartActivity(
$"WireMock {requestMethod} {requestPath}",
ActivityKind.Server
);
return activity;
}
/// <summary>
/// Enriches an activity with request information.
/// </summary>
internal static void EnrichWithRequest(Activity? activity, IRequestMessage request, ActivityTracingOptions? options = null)
{
if (activity == null)
{
return;
}
activity.SetTag(WireMockSemanticConventions.HttpMethod, request.Method);
activity.SetTag(WireMockSemanticConventions.HttpUrl, request.Url);
activity.SetTag(WireMockSemanticConventions.HttpPath, request.Path);
activity.SetTag(WireMockSemanticConventions.HttpHost, request.Host);
if (request.ClientIP != null)
{
activity.SetTag(WireMockSemanticConventions.ClientAddress, request.ClientIP);
}
// Record request body if enabled
if (options?.RecordRequestBody == true && request.Body != null)
{
activity.SetTag(WireMockSemanticConventions.RequestBody, request.Body);
}
}
/// <summary>
/// Enriches an activity with response information.
/// </summary>
internal static void EnrichWithResponse(Activity? activity, IResponseMessage? response, ActivityTracingOptions? options = null)
{
if (activity == null || response == null)
{
return;
}
// StatusCode can be int, HttpStatusCode, or string
var statusCode = response.StatusCode;
int? statusCodeInt = statusCode switch
{
int i => i,
System.Net.HttpStatusCode hsc => (int)hsc,
string s when int.TryParse(s, out var parsed) => parsed,
_ => null
};
if (statusCodeInt.HasValue)
{
activity.SetTag(WireMockSemanticConventions.HttpStatusCode, statusCodeInt.Value);
activity.SetTag("otel.status_description", $"HTTP {statusCodeInt.Value}");
// Set status based on HTTP status code (using standard otel.status_code tag)
if (statusCodeInt.Value >= 400)
{
activity.SetTag("otel.status_code", "ERROR");
}
else
{
activity.SetTag("otel.status_code", "OK");
}
}
// Record response body if enabled
if (options?.RecordResponseBody == true && response.BodyData?.BodyAsString != null)
{
activity.SetTag(WireMockSemanticConventions.ResponseBody, response.BodyData.BodyAsString);
}
}
/// <summary>
/// Enriches an activity with mapping match information.
/// </summary>
internal static void EnrichWithMappingMatch(
Activity? activity,
Guid? mappingGuid,
string? mappingTitle,
bool isPerfectMatch,
double? matchScore)
{
if (activity == null)
{
return;
}
activity.SetTag(WireMockSemanticConventions.MappingMatched, isPerfectMatch);
if (mappingGuid.HasValue)
{
activity.SetTag(WireMockSemanticConventions.MappingGuid, mappingGuid.Value.ToString());
}
if (!string.IsNullOrEmpty(mappingTitle))
{
activity.SetTag(WireMockSemanticConventions.MappingTitle, mappingTitle);
}
if (matchScore.HasValue)
{
activity.SetTag(WireMockSemanticConventions.MatchScore, matchScore.Value);
}
}
/// <summary>
/// Enriches an activity with log entry information (includes response and mapping match info).
/// </summary>
internal static void EnrichWithLogEntry(Activity? activity, ILogEntry logEntry, ActivityTracingOptions? options = null)
{
if (activity == null)
{
return;
}
// Enrich with response
EnrichWithResponse(activity, logEntry.ResponseMessage, options);
// Enrich with mapping match (if enabled)
if (options?.RecordMatchDetails != false)
{
EnrichWithMappingMatch(
activity,
logEntry.MappingGuid,
logEntry.MappingTitle,
logEntry.RequestMatchResult?.IsPerfectMatch ?? false,
logEntry.RequestMatchResult?.TotalScore);
}
// Set request GUID
activity.SetTag(WireMockSemanticConventions.RequestGuid, logEntry.Guid.ToString());
}
/// <summary>
/// Records an exception on the activity.
/// </summary>
internal static void RecordException(Activity? activity, Exception exception)
{
if (activity == null)
{
return;
}
// Use standard OpenTelemetry exception semantic conventions
activity.SetTag("otel.status_code", "ERROR");
activity.SetTag("otel.status_description", exception.Message);
activity.SetTag("exception.type", exception.GetType().FullName);
activity.SetTag("exception.message", exception.Message);
activity.SetTag("exception.stacktrace", exception.ToString());
}
}
#endif

View File

@@ -0,0 +1,28 @@
// Copyright © WireMock.Net
namespace WireMock.Owin.ActivityTracing;
/// <summary>
/// Semantic convention constants for WireMock.Net tracing attributes.
/// </summary>
internal static class WireMockSemanticConventions
{
// Standard HTTP semantic conventions (OpenTelemetry)
public const string HttpMethod = "http.request.method";
public const string HttpUrl = "url.full";
public const string HttpPath = "url.path";
public const string HttpHost = "server.address";
public const string HttpStatusCode = "http.response.status_code";
public const string ClientAddress = "client.address";
// WireMock-specific attributes
public const string MappingMatched = "wiremock.mapping.matched";
public const string MappingGuid = "wiremock.mapping.guid";
public const string MappingTitle = "wiremock.mapping.title";
public const string MatchScore = "wiremock.match.score";
public const string PartialMappingGuid = "wiremock.partial_mapping.guid";
public const string PartialMappingTitle = "wiremock.partial_mapping.title";
public const string RequestGuid = "wiremock.request.guid";
public const string RequestBody = "wiremock.request.body";
public const string ResponseBody = "wiremock.response.body";
}

View File

@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Owin.ActivityTracing;
using WireMock.Types;
using WireMock.Util;
using System.Security.Cryptography.X509Certificates;
@@ -90,4 +91,12 @@ internal interface IWireMockMiddlewareOptions
QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
public bool ProxyAll { get; set; }
#if ACTIVITY_TRACING_SUPPORTED
/// <summary>
/// Gets or sets the activity tracing options.
/// When set, System.Diagnostics.Activity objects are created for request tracing.
/// </summary>
ActivityTracingOptions? ActivityTracingOptions { get; set; }
#endif
}

View File

@@ -178,9 +178,12 @@ namespace WireMock.Owin.Mappers
return (bodyData.Encoding ?? _utf8NoBom).GetBytes(jsonBody);
case BodyType.ProtoBuf:
var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
var protoBufUtils = TypeLoader.LoadStaticInstance<IProtoBufUtils>();
return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
if (TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
{
var protoDefinitions = bodyData.ProtoDefinition?.Invoke().Texts;
return await protoBufUtils.GetProtoBufMessageWithHeaderAsync(protoDefinitions, bodyData.ProtoBufMessageType, bodyData.BodyAsJson).ConfigureAwait(false);
}
break;
case BodyType.Bytes:
return bodyData.BodyAsBytes;

View File

@@ -9,16 +9,10 @@ using WireMock.Services;
namespace WireMock.Owin;
internal class MappingMatcher : IMappingMatcher
internal class MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetween0And1 randomizerDoubleBetween0And1) : IMappingMatcher
{
private readonly IWireMockMiddlewareOptions _options;
private readonly IRandomizerDoubleBetween0And1 _randomizerDoubleBetween0And1;
public MappingMatcher(IWireMockMiddlewareOptions options, IRandomizerDoubleBetween0And1 randomizerDoubleBetween0And1)
{
_options = Guard.NotNull(options);
_randomizerDoubleBetween0And1 = Guard.NotNull(randomizerDoubleBetween0And1);
}
private readonly IWireMockMiddlewareOptions _options = Guard.NotNull(options);
private readonly IRandomizerDoubleBetween0And1 _randomizerDoubleBetween0And1 = Guard.NotNull(randomizerDoubleBetween0And1);
public (MappingMatcherResult? Match, MappingMatcherResult? Partial) FindBestMatch(RequestMessage request)
{
@@ -28,7 +22,7 @@ internal class MappingMatcher : IMappingMatcher
var mappings = _options.Mappings.Values
.Where(m => m.TimeSettings.IsValid())
.Where(m => m.Probability is null || m.Probability <= _randomizerDoubleBetween0And1.Generate())
.Where(m => m.Probability is null || _randomizerDoubleBetween0And1.Generate() <= m.Probability)
.ToArray();
foreach (var mapping in mappings)
@@ -41,10 +35,10 @@ internal class MappingMatcher : IMappingMatcher
var exceptions = mappingMatcherResult.RequestMatchResult.MatchDetails
.Where(md => md.Exception != null)
.Select(md => md.Exception)
.Select(md => md.Exception!)
.ToArray();
if (!exceptions.Any())
if (exceptions.Length == 0)
{
possibleMappings.Add(mappingMatcherResult);
}
@@ -52,7 +46,7 @@ internal class MappingMatcher : IMappingMatcher
{
foreach (var ex in exceptions)
{
LogException(mapping, ex!);
LogException(mapping, ex);
}
}
}
@@ -62,14 +56,16 @@ internal class MappingMatcher : IMappingMatcher
}
}
var partialMappings = possibleMappings
var partialMatches = possibleMappings
.Where(pm => (pm.Mapping.IsAdminInterface && pm.RequestMatchResult.IsPerfectMatch) || !pm.Mapping.IsAdminInterface)
.OrderBy(m => m.RequestMatchResult)
.ThenBy(m => m.RequestMatchResult.TotalNumber)
.ThenBy(m => m.Mapping.Priority)
.ThenByDescending(m => m.Mapping.Probability)
.ThenByDescending(m => m.Mapping.UpdatedAt)
.ToList();
var partialMatch = partialMappings.FirstOrDefault(pm => pm.RequestMatchResult.AverageTotalScore > 0.0);
.Where(pm => pm.RequestMatchResult.AverageTotalScore > 0.0)
.ToArray();
var partialMatch = partialMatches.FirstOrDefault();
if (_options.AllowPartialMapping == true)
{
@@ -78,7 +74,11 @@ internal class MappingMatcher : IMappingMatcher
var match = possibleMappings
.Where(m => m.RequestMatchResult.IsPerfectMatch)
.OrderBy(m => m.Mapping.Priority).ThenBy(m => m.RequestMatchResult).ThenByDescending(m => m.Mapping.UpdatedAt)
.OrderBy(m => m.Mapping.Priority)
.ThenBy(m => m.RequestMatchResult)
.ThenBy(m => m.RequestMatchResult.TotalNumber)
.ThenByDescending(m => m.Mapping.Probability)
.ThenByDescending(m => m.Mapping.UpdatedAt)
.FirstOrDefault();
return (match, partialMatch);

View File

@@ -16,6 +16,10 @@ using System.Collections.Generic;
using WireMock.Constants;
using WireMock.Exceptions;
using WireMock.Util;
#if ACTIVITY_TRACING_SUPPORTED
using System.Diagnostics;
using WireMock.Owin.ActivityTracing;
#endif
#if !USE_ASPNETCORE
using IContext = Microsoft.Owin.IOwinContext;
using OwinMiddleware = Microsoft.Owin.OwinMiddleware;
@@ -97,6 +101,40 @@ namespace WireMock.Owin
{
var request = await _requestMapper.MapAsync(ctx.Request, _options).ConfigureAwait(false);
#if ACTIVITY_TRACING_SUPPORTED
// Start activity if ActivityTracingOptions is configured
var tracingEnabled = _options.ActivityTracingOptions is not null;
var excludeAdmin = _options.ActivityTracingOptions?.ExcludeAdminRequests ?? true;
Activity? activity = null;
// Check if we should trace this request (optionally exclude admin requests)
var shouldTrace = tracingEnabled && !(excludeAdmin && request.Path.StartsWith("/__admin/"));
if (shouldTrace)
{
activity = WireMockActivitySource.StartRequestActivity(request.Method, request.Path);
WireMockActivitySource.EnrichWithRequest(activity, request, _options.ActivityTracingOptions);
}
try
{
await InvokeInternalCoreAsync(ctx, request, activity).ConfigureAwait(false);
}
finally
{
activity?.Dispose();
}
#else
await InvokeInternalCoreAsync(ctx, request).ConfigureAwait(false);
#endif
}
#if ACTIVITY_TRACING_SUPPORTED
private async Task InvokeInternalCoreAsync(IContext ctx, RequestMessage request, Activity? activity)
#else
private async Task InvokeInternalCoreAsync(IContext ctx, RequestMessage request)
#endif
{
var logRequest = false;
IResponseMessage? response = null;
(MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, null);
@@ -193,6 +231,10 @@ namespace WireMock.Owin
{
_options.Logger.Error($"Providing a Response for Mapping '{result.Match?.Mapping.Guid}' failed. HttpStatusCode set to 500. Exception: {ex}");
response = ResponseMessageBuilder.Create(500, ex.Message);
#if ACTIVITY_TRACING_SUPPORTED
WireMockActivitySource.RecordException(activity, ex);
#endif
}
finally
{
@@ -211,6 +253,11 @@ namespace WireMock.Owin
PartialMatchResult = result.Partial?.RequestMatchResult
};
#if ACTIVITY_TRACING_SUPPORTED
// Enrich activity with response and mapping info
WireMockActivitySource.EnrichWithLogEntry(activity, log, _options.ActivityTracingOptions);
#endif
LogRequest(log, logRequest);
try

View File

@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
using WireMock.Handlers;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Owin.ActivityTracing;
using WireMock.Types;
using WireMock.Util;
using System.Security.Cryptography.X509Certificates;
@@ -106,4 +107,9 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc />
public bool ProxyAll { get; set; }
#if ACTIVITY_TRACING_SUPPORTED
/// <inheritdoc />
public ActivityTracingOptions? ActivityTracingOptions { get; set; }
#endif
}

View File

@@ -2,6 +2,7 @@
using System;
using Stef.Validation;
using WireMock.Owin.ActivityTracing;
using WireMock.Settings;
namespace WireMock.Owin;
@@ -34,6 +35,27 @@ internal static class WireMockMiddlewareOptionsHelper
options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests;
// Validate and configure activity tracing
ActivityTracingValidator.ValidateActivityApiPresence(settings);
#if ACTIVITY_TRACING_SUPPORTED
if (settings.ActivityTracingOptions is not null)
{
options.ActivityTracingOptions = new Owin.ActivityTracing.ActivityTracingOptions
{
ExcludeAdminRequests = settings.ActivityTracingOptions.ExcludeAdminRequests,
RecordRequestBody = settings.ActivityTracingOptions.RecordRequestBody,
RecordResponseBody = settings.ActivityTracingOptions.RecordResponseBody,
RecordMatchDetails = settings.ActivityTracingOptions.RecordMatchDetails
};
}
#endif
#if USE_ASPNETCORE
options.AdditionalServiceRegistration = settings.AdditionalServiceRegistration;
options.CorsPolicyOptions = settings.CorsPolicyOptions;
options.ClientCertificateMode = settings.ClientCertificateMode;
options.AcceptAnyClientCertificate = settings.AcceptAnyClientCertificate;
#endif
if (settings.CustomCertificateDefined)
{
options.X509StoreName = settings.CertificateSettings!.X509StoreName;

View File

@@ -13,16 +13,10 @@ using WireMock.Util;
namespace WireMock.Proxy;
internal class ProxyHelper
internal class ProxyHelper(WireMockServerSettings settings)
{
private readonly WireMockServerSettings _settings;
private readonly ProxyMappingConverter _proxyMappingConverter;
public ProxyHelper(WireMockServerSettings settings)
{
_settings = Guard.NotNull(settings);
_proxyMappingConverter = new ProxyMappingConverter(settings, new GuidUtils(), new DateTimeUtils());
}
private readonly WireMockServerSettings _settings = Guard.NotNull(settings);
private readonly ProxyMappingConverter _proxyMappingConverter = new(settings, new GuidUtils(), new DateTimeUtils());
public async Task<(IResponseMessage Message, IMapping? Mapping)> SendAsync(
IMapping? mapping,
@@ -39,18 +33,7 @@ internal class ProxyHelper
var requiredUri = new Uri(url);
// Create HttpRequestMessage
var replaceSettings = proxyAndRecordSettings.ReplaceSettings;
string proxyUrl;
if (replaceSettings is not null)
{
var stringComparison = replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
proxyUrl = url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, stringComparison);
}
else
{
proxyUrl = url;
}
var proxyUrl = proxyAndRecordSettings.ReplaceSettings != null ? ProxyUrlTransformer.Transform(_settings, proxyAndRecordSettings.ReplaceSettings, url) : url;
var httpRequestMessage = HttpRequestMessageHelper.Create(requestMessage, proxyUrl);
// Call the URL

View File

@@ -0,0 +1,21 @@
// Copyright © WireMock.Net
using System;
using WireMock.Settings;
using WireMock.Transformers;
namespace WireMock.Proxy;
internal static class ProxyUrlTransformer
{
internal static string Transform(WireMockServerSettings settings, ProxyUrlReplaceSettings replaceSettings, string url)
{
if (!replaceSettings.UseTransformer)
{
return url.Replace(replaceSettings.OldValue, replaceSettings.NewValue, replaceSettings.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
var transformer = TransformerFactory.Create(replaceSettings.TransformerType, settings);
return transformer.Transform(replaceSettings.TransformTemplate, url);
}
}

View File

@@ -34,13 +34,6 @@ public partial class Request
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body);
return WithBody([matcher]);
}
/// <inheritdoc />
public IRequestBuilder WithBody(IMatcher matcher)
{
@@ -98,4 +91,20 @@ public partial class Request
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this;
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsJson(object body, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch)
{
var matcher = body as IMatcher ?? new JsonMatcher(matchBehaviour, body);
return WithBody([matcher]);
}
/// <inheritdoc />
public IRequestBuilder WithBodyAsType<T>(Func<T?, bool> func)
{
Guard.NotNull(func);
_requestMatchers.Add(new RequestMessageBodyMatcher<T>(func));
return this;
}
}

View File

@@ -15,7 +15,7 @@ public partial class Request
}
/// <inheritdoc />
public IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.Or)
public IRequestBuilder WithMultiPart(IMatcher[] matchers, MatchBehaviour matchBehaviour = MatchBehaviour.AcceptOnMatch, MatchOperator matchOperator = MatchOperator.And)
{
_requestMatchers.Add(new RequestMessageMultiPartMatcher(matchBehaviour, matchOperator, matchers));
return this;

View File

@@ -4,6 +4,7 @@ using System;
using Stef.Validation;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.Validators;
namespace WireMock.RequestBuilders;
@@ -20,7 +21,7 @@ public partial class Request
{
Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers));
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this;
}
@@ -34,6 +35,10 @@ public partial class Request
public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths)
{
Guard.NotNullOrEmpty(paths);
foreach (var path in paths)
{
PathValidator.ValidateAndThrow(path, nameof(paths));
}
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths));
return this;

View File

@@ -29,7 +29,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder Create()
{
return new Request(new List<IRequestMatcher>());
return new Request([]);
}
/// <summary>

View File

@@ -183,16 +183,9 @@ public class RequestMessage : IRequestMessage
#endif
#if MIMEKIT
try
if (TypeLoader.TryLoadStaticInstance<IMimeKitUtils>(out var mimeKitUtils) && mimeKitUtils.TryGetMimeMessage(this, out var mimeMessage))
{
if (TypeLoader.LoadStaticInstance<IMimeKitUtils>().TryGetMimeMessage(this, out var mimeMessage))
{
BodyAsMimeMessage = mimeMessage;
}
}
catch
{
// Ignore exception from MimeMessage.Load
BodyAsMimeMessage = mimeMessage;
}
#endif
}

View File

@@ -272,25 +272,8 @@ public partial class Response : IResponseBuilder
}
}
ITransformer responseMessageTransformer;
switch (TransformerType)
{
case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(settings);
responseMessageTransformer = new Transformer(settings, factoryHandlebars);
break;
case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, TransformerType);
responseMessageTransformer = new Transformer(settings, factoryDotLiquid);
break;
default:
throw new NotSupportedException($"TransformerType '{TransformerType}' is not supported.");
}
return (responseMessageTransformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
var transformer = TransformerFactory.Create(TransformerType, settings);
return (transformer.Transform(mapping, requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
}
if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true && responseMessage.BodyData?.BodyAsFile is not null)

View File

@@ -166,11 +166,12 @@ internal class LogEntryMapper
TotalScore = matchResult.TotalScore,
TotalNumber = matchResult.TotalNumber,
AverageTotalScore = matchResult.AverageTotalScore,
MatchDetails = matchResult.MatchDetails.Select(md => new
{
Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty),
md.Score
} as object).ToList()
MatchDetails = matchResult.MatchDetails
//MatchDetails = matchResult.MatchDetails.Select(md => new
//{
// Name = md.MatcherType.Name.Replace("RequestMessage", string.Empty),
// md.Score
//} as object).ToList()
};
}
}

View File

@@ -226,14 +226,14 @@ internal class MappingConverter(MatcherMapper mapper)
}
}
if (response.Delay is { })
{
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
}
else if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
{
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
}
else if (response.Delay is { })
{
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
}
if (response.UseTransformer)
{

View File

@@ -25,9 +25,9 @@ internal class MatcherMapper
_settings = Guard.NotNull(settings);
}
public IMatcher[]? Map(IEnumerable<MatcherModel>? matchers)
public IMatcher[] Map(IEnumerable<MatcherModel>? matchers)
{
return matchers?.Select(Map).OfType<IMatcher>().ToArray();
return matchers?.Select(Map).OfType<IMatcher>().ToArray() ?? [];
}
public IMatcher? Map(MatcherModel? matcherModel)
@@ -55,7 +55,12 @@ internal class MatcherMapper
case "CSharpCodeMatcher":
if (_settings.AllowCSharpCodeMatcher == true)
{
return TypeLoader.LoadNewInstance<ICSharpCodeMatcher>(matchBehaviour, matchOperator, stringPatterns);
if (TypeLoader.TryLoadNewInstance<ICSharpCodeMatcher>(out var csharpCodeMatcher, matchBehaviour, matchOperator, stringPatterns))
{
return csharpCodeMatcher;
}
throw new InvalidOperationException("The 'CSharpCodeMatcher' cannot be loaded. Please install the WireMock.Net.Matchers.CSharpCode package.");
}
throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'.");
@@ -75,7 +80,12 @@ internal class MatcherMapper
case "GraphQLMatcher":
var patternAsString = stringPatterns[0].GetPattern();
var schema = new AnyOf<string, StringPattern, ISchemaData>(patternAsString);
return TypeLoader.LoadNewInstance<IGraphQLMatcher>(schema, matcherModel.CustomScalars, matchBehaviour, matchOperator);
if (TypeLoader.TryLoadNewInstance<IGraphQLMatcher>(out var graphQLMatcher, schema, matcherModel.CustomScalars, matchBehaviour, matchOperator))
{
return graphQLMatcher;
}
throw new InvalidOperationException("The 'GraphQLMatcher' cannot be loaded. Please install the WireMock.Net.GraphQL package.");
case "MimePartMatcher":
return CreateMimePartMatcher(matchBehaviour, matcherModel);
@@ -282,18 +292,34 @@ internal class MatcherMapper
var contentTransferEncodingMatcher = Map(matcher.ContentTransferEncodingMatcher) as IStringMatcher;
var contentMatcher = Map(matcher.ContentMatcher);
return TypeLoader.LoadNewInstance<IMimePartMatcher>(matchBehaviour, contentTypeMatcher, contentDispositionMatcher, contentTransferEncodingMatcher, contentMatcher);
if (TypeLoader.TryLoadNewInstance<IMimePartMatcher>(
out var mimePartMatcher,
matchBehaviour,
contentTypeMatcher,
contentDispositionMatcher,
contentTransferEncodingMatcher,
contentMatcher))
{
return mimePartMatcher;
}
throw new InvalidOperationException("The 'MimePartMatcher' cannot be loaded. Please install the WireMock.Net.MimePart package.");
}
private IProtoBufMatcher CreateProtoBufMatcher(MatchBehaviour? matchBehaviour, IReadOnlyList<string> protoDefinitions, MatcherModel matcher)
{
var objectMatcher = Map(matcher.ContentMatcher) as IObjectMatcher;
return TypeLoader.LoadNewInstance<IProtoBufMatcher>(
if (TypeLoader.TryLoadNewInstance<IProtoBufMatcher>(
out var protobufMatcher,
() => ProtoDefinitionUtils.GetIdOrTexts(_settings, protoDefinitions.ToArray()),
matcher.ProtoBufMessageType!,
matchBehaviour ?? MatchBehaviour.AcceptOnMatch,
objectMatcher
);
objectMatcher))
{
return protobufMatcher;
}
throw new InvalidOperationException("The 'ProtoBufMatcher' cannot be loaded. Please install the WireMock.Net.ProtoBuf package.");
}
}

View File

@@ -6,6 +6,7 @@ using System.Linq;
using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Serialization;
@@ -253,7 +254,15 @@ public partial class WireMockServer
else if (requestModel.Body?.Matchers != null)
{
var matchOperator = StringUtils.ParseMatchOperator(requestModel.Body.MatchOperator);
requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers)!, matchOperator);
if (requestModel.Body.MatcherName == RequestMessageMultiPartMatcher.Name)
{
requestBuilder = requestBuilder.WithMultiPart(_matcherMapper.Map(requestModel.Body.Matchers), matchOperator: matchOperator);
}
else
{
requestBuilder = requestBuilder.WithBody(_matcherMapper.Map(requestModel.Body.Matchers), matchOperator);
}
}
return requestBuilder;
@@ -366,10 +375,8 @@ public partial class WireMockServer
}
else if (responseModel.BodyAsJson != null)
{
if (responseModel.ProtoBufMessageType != null)
if (responseModel.ProtoBufMessageType != null && TypeLoader.TryLoadStaticInstance<IProtoBufUtils>(out var protoBufUtils))
{
var protoBufUtils = TypeLoader.LoadStaticInstance<IProtoBufUtils>();
if (responseModel.ProtoDefinition != null)
{
responseBuilder = protoBufUtils.UpdateResponseBuilder(responseBuilder, responseModel.ProtoBufMessageType, responseModel.BodyAsJson, responseModel.ProtoDefinition);

View File

@@ -412,11 +412,6 @@ public partial class WireMockServer : IWireMockServer
);
#if USE_ASPNETCORE
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
_options.CorsPolicyOptions = _settings.CorsPolicyOptions;
_options.ClientCertificateMode = _settings.ClientCertificateMode;
_options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
_httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else
_httpServer = new OwinSelfHost(_options, urlOptions);

View File

@@ -36,7 +36,7 @@ public static class WireMockServerSettingsParser
if (parser.GetBoolSwitchValue("help"))
{
(logger ?? new WireMockConsoleLogger()).Info("See https://github.com/wiremock/WireMock.Net/wiki/WireMock-commandline-parameters for details on all commandline options.");
(logger ?? new WireMockConsoleLogger()).Info("See https://wiremock.org/dotnet/wiremock-commandline-parameters/ for details on all commandline options.");
settings = null;
return false;
}
@@ -85,6 +85,7 @@ public static class WireMockServerSettingsParser
ParseProxyAndRecordSettings(settings, parser);
ParseCertificateSettings(settings, parser);
ParseHandlebarsSettings(settings, parser);
ParseActivityTracingSettings(settings, parser);
return true;
}
@@ -153,7 +154,7 @@ public static class WireMockServerSettingsParser
}
else if (settings.HostingScheme is null)
{
settings.Urls = parser.GetValues("Urls", ["http://*:9091/"]);
settings.Urls = parser.GetValues(nameof(WireMockServerSettings.Urls), defaultValue: ["http://*:9091/"]);
}
}
@@ -202,14 +203,42 @@ public static class WireMockServerSettingsParser
private static void ParseProxyUrlReplaceSettings(ProxyAndRecordSettings settings, SimpleSettingsParser parser)
{
var proxyUrlReplaceOldValue = parser.GetStringValue("ProxyUrlReplaceOldValue");
var proxyUrlReplaceNewValue = parser.GetStringValue("ProxyUrlReplaceNewValue");
const string prefix = "ProxyUrlReplace";
var proxyUrlReplaceOldValue = parser.GetStringValue($"{prefix}OldValue");
var proxyUrlReplaceNewValue = parser.GetStringValue($"{prefix}NewValue");
if (!string.IsNullOrEmpty(proxyUrlReplaceOldValue) && proxyUrlReplaceNewValue != null)
{
settings.ReplaceSettings = new ProxyUrlReplaceSettings
{
OldValue = proxyUrlReplaceOldValue!,
NewValue = proxyUrlReplaceNewValue
OldValue = proxyUrlReplaceOldValue,
NewValue = proxyUrlReplaceNewValue,
IgnoreCase = parser.GetBoolValue($"{prefix}IgnoreCase")
};
return;
}
var transformTemplate = parser.GetStringValue($"{prefix}TransformTemplate");
if (!string.IsNullOrEmpty(transformTemplate))
{
settings.ReplaceSettings = new ProxyUrlReplaceSettings
{
TransformTemplate = transformTemplate,
TransformerType = parser.GetEnumValue($"{prefix}TransformerType", TransformerType.Handlebars)
};
}
}
private static void ParseActivityTracingSettings(WireMockServerSettings settings, SimpleSettingsParser parser)
{
// Only create ActivityTracingOptions if tracing is enabled
if (parser.GetBoolValue("ActivityTracingEnabled") || parser.GetBoolValue("ActivityTracingOptions__Enabled"))
{
settings.ActivityTracingOptions = new ActivityTracingOptions
{
ExcludeAdminRequests = parser.GetBoolWithDefault("ActivityTracingExcludeAdminRequests", "ActivityTracingOptions__ExcludeAdminRequests", defaultValue: true),
RecordRequestBody = parser.GetBoolValue("ActivityTracingRecordRequestBody") || parser.GetBoolValue("ActivityTracingOptions__RecordRequestBody"),
RecordResponseBody = parser.GetBoolValue("ActivityTracingRecordResponseBody") || parser.GetBoolValue("ActivityTracingOptions__RecordResponseBody"),
RecordMatchDetails = parser.GetBoolWithDefault("ActivityTracingRecordMatchDetails", "ActivityTracingOptions__RecordMatchDetails", defaultValue: true)
};
}
}

View File

@@ -39,6 +39,8 @@ internal static class WireMockHandlebarsHelpers
#endif
o.CustomHelperPaths = paths;
o.Categories = settings.HandlebarsSettings?.AllowedHandlebarsHelpers ?? HandlebarsSettings.DefaultAllowedHandlebarsHelpers;
o.CustomHelpers = new Dictionary<string, IHelpers>();
if (settings.HandlebarsSettings?.AllowedCustomHandlebarsHelpers.HasFlag(CustomHandlebarsHelpers.File) == true)
{

View File

@@ -6,7 +6,7 @@ using WireMock.Util;
namespace WireMock.Transformers;
interface ITransformer
internal interface ITransformer
{
ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options);
@@ -15,4 +15,6 @@ interface ITransformer
IDictionary<string, WireMockList<string>> TransformHeaders(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, IDictionary<string, WireMockList<string>>? headers);
string TransformString(IMapping mapping, IRequestMessage originalRequestMessage, IResponseMessage originalResponseMessage, string? value);
string Transform(string template, object? model);
}

View File

@@ -1,9 +1,8 @@
// Copyright © WireMock.Net
namespace WireMock.Transformers
namespace WireMock.Transformers;
internal interface ITransformerContextFactory
{
interface ITransformerContextFactory
{
ITransformerContext Create();
}
ITransformerContext Create();
}

View File

@@ -75,6 +75,11 @@ internal class Transformer : ITransformer
return transformerContext.ParseAndRender(value, model);
}
public string Transform(string template, object? model)
{
return model is null ? string.Empty : _factory.Create().ParseAndRender(template, model);
}
public ResponseMessage Transform(IMapping mapping, IRequestMessage requestMessage, IResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options)
{
var responseMessage = new ResponseMessage();

View File

@@ -0,0 +1,30 @@
// Copyright © WireMock.Net
using System;
using WireMock.Settings;
using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban;
using WireMock.Types;
namespace WireMock.Transformers;
internal static class TransformerFactory
{
internal static ITransformer Create(TransformerType transformerType, WireMockServerSettings settings)
{
switch (transformerType)
{
case TransformerType.Handlebars:
var factoryHandlebars = new HandlebarsContextFactory(settings);
return new Transformer(settings, factoryHandlebars);
case TransformerType.Scriban:
case TransformerType.ScribanDotLiquid:
var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, transformerType);
return new Transformer(settings, factoryDotLiquid);
default:
throw new NotSupportedException($"{nameof(TransformerType)} '{transformerType}' is not supported.");
}
}
}

Some files were not shown because too many files have changed in this diff Show More