Compare commits

...

40 Commits

Author SHA1 Message Date
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
120 changed files with 5558 additions and 8120 deletions
+36
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
+51
View File
@@ -1,3 +1,54 @@
# 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) # 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) - [#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) - [#1370](https://github.com/wiremock/WireMock.Net/pull/1370) - Support Testcontainers 4.8.0 [bug] contributed by [MD-V](https://github.com/MD-V)
+1 -1
View File
@@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Version>1.15.0</Version> <VersionPrefix>1.24.0</VersionPrefix>
<PackageIcon>WireMock.Net-Logo.png</PackageIcon> <PackageIcon>WireMock.Net-Logo.png</PackageIcon>
<PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/wiremock/WireMock.Net</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+1 -1
View File
@@ -1,6 +1,6 @@
rem https://github.com/StefH/GitHubReleaseNotes rem https://github.com/StefH/GitHubReleaseNotes
SET version=1.15.0 SET version=1.24.0
GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN% GitHubReleaseNotes --output CHANGELOG.md --skip-empty-releases --exclude-labels wontfix test question invalid doc duplicate example environment --version %version% --token %GH_TOKEN%
+13 -13
View File
@@ -15,48 +15,48 @@ Lightweight Http Mocking Server for .NET, inspired by WireMock.org (from the Jav
### :star: Stubbing ### :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria. 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 ### :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 ### :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 ### :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 ### :star: Using
WireMock.Net can be used in several ways: WireMock.Net can be used in several ways:
#### UnitTesting #### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see 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 ### 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 ### 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 #### 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 #### 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 #### 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 #### 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 #### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath). 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). For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
#### HTTPS / SSL #### 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 ## :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/).
+4 -4
View File
@@ -1,6 +1,6 @@
# 1.15.0 (22 October 2025) # 1.24.0 (18 January 2026)
- #1367 Fix WithProbability logic [bug] - #1417 Update aspire to 13.1 (examples + code) [feature]
- #1370 Support Testcontainers 4.8.0 [bug] - #1418 Add OTEL tracing support for Wiremock + automatic OTEL for Aspire integration [feature]
- #1126 Request matching WithProbability strange behaviour [bug] - #1214 OpenTelemetry Support for .NET Aspire [feature]
The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md The full release notes can be found here: https://github.com/wiremock/WireMock.Net/blob/master/CHANGELOG.md
+24 -18
View File
@@ -3,7 +3,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
--- ---
:books: <strong>Full documentation can now be found at <a href="https://wiremock.org/dotnet/" title="WireMock.Net docs">wiremock.org</a> ### :books: Full documentation can now be found at [wiremock.org](https://wiremock.org/dotnet)
--- ---
@@ -33,7 +33,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| &nbsp;&nbsp;**Issues** | [![GitHub issues](https://img.shields.io/github/issues/WireMock-Net/WireMock.Net.svg)](https://github.com/wiremock/WireMock.Net/issues) | | &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; | | ***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;**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;**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)| | &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)|
@@ -41,7 +41,7 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
### :package: NuGet packages ### :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** | [![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) | &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)
@@ -53,7 +53,9 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| &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.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.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** | [![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.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.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) | &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)
@@ -61,13 +63,14 @@ A C# .NET version based on [mock4net](https://github.com/alexvictoor/mock4net) w
| &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.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.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.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.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) | &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 /> <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*.
--- ---
@@ -93,52 +96,55 @@ To still enable this feature, you need to add the `Environment` category to the
--- ---
## :memo: Development ## :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 ## :star: Stubbing
A core feature of WireMock.Net is the ability to return predefined HTTP responses for requests matching criteria. 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 ## :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 ## :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 ## :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 ## :star: Using
WireMock.Net can be used in several ways: WireMock.Net can be used in several ways:
### UnitTesting ### UnitTesting
You can use your favorite test framework and use WireMock within your tests, see 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 ### 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 ### 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 ### 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 ### 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 ### 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 ### 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 ### In a docker container
There is also a Linux and Windows-Nano container available at [hub.docker.com](https://hub.docker.com/r/sheyenrath). 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). For more details see also [Docker](https://github.com/wiremock/WireMock.Net-docker).
#### HTTPS / SSL ### 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 documentation page: [What is WireMock.Net](https://wiremock.org/dotnet/what-is-wiremock-net/).
--- ---
+466 -2
View File
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 18
VisualStudioVersion = 17.0.31521.260 VisualStudioVersion = 18.0.11205.157
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8F890C6F-9ACC-438D-928A-AD61CDA862F2}"
EndProject EndProject
@@ -144,212 +144,672 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Extensions.Rou
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ProtoBuf", "src\WireMock.Net.ProtoBuf\WireMock.Net.ProtoBuf.csproj", "{B47413AA-55D3-49A7-896A-17ADBFF72407}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ProtoBuf", "src\WireMock.Net.ProtoBuf\WireMock.Net.ProtoBuf.csproj", "{B47413AA-55D3-49A7-896A-17ADBFF72407}"
EndProject 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
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D3804228-91F4-4502-9595-39584E5A01AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {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|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.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A01AD}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A94}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D6-8B9A-579DED3D9A96}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B6269AAC-170A-43D5-8B9A-579DED3D9A95}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{31DC2EF8-C3FE-467D-84BE-FB5D956E612E}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{74D91AD0-D96D-4FD2-AEC5-CC49D38346C0}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7F0B2446-0363-4720-AF46-F47F83B557DC}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{A9D039B9-7509-4CF1-9EFD-87EB82998575}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{5C09FB93-1535-4F92-AF26-21E8A061EE4A}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A95}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{40BF24B5-12E6-4610-9489-138798632E28}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A44}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{08B29DB1-FEFE-408A-AD0A-6BA6DDC8D70F}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{3BA5109E-5F30-4CC2-B699-02EC82560AA6}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{3F7AA023-6833-4856-A08A-4B5717B592B8}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{670C7562-C154-442E-A249-7D26849BCD13}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{0DE0954F-8C00-4E8D-B94A-4361FC1CBE44}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7C2A9DE8-C89F-4841-9058-6B9BF81E5E34}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{BAA9EC2A-874B-45CE-8E51-A73622DC7F3D}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{12B016A5-9D8B-4EFE-96C2-CA51BE43367D}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{56A38798-C48B-4A4A-B805-071E05C02CE1}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{07C30227-ADEC-4BDE-8CDC-849D85A690BB}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{1EA72C0F-92E9-486B-8FFE-53F992BFC4AA}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7FC0B409-2682-40EE-B3B9-3930D6769D01}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B1580A38-84E7-44BE-8FE7-3EE5031D74A1}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{CAB42D88-B4E4-4887-B684-9F3E09D085A1}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{42113E6B-DC43-4E80-9967-1E4233568E87}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{84624E1F-DF07-4315-89B0-51776BE99E13}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{A34F1575-7C33-4548-8CEF-8D8D8B84153C}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7373B7DC-47ED-45A5-969D-D7DDBA529B53}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{CE602F57-FEF8-4559-A9E0-6200BE1BF398}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{F1B5999D-D22E-48A6-AB86-18A7876BD32E}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{C9210DA3-F390-4598-8512-349A473FE9C9}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{91024A93-848F-4A02-AF53-5EBE5834E23C}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{4CD237F7-B616-46B8-872F-E49B4BBB3EAE}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{E72ADFAB-4B42-439E-B1EE-C06E504B35D2}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-579DED3D9A13}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{6B30AA9F-DA04-4EB5-B03C-45A8EF272ECE}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{A5FEF4F7-7DA2-4962-89A8-16BA942886E5}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{7753670F-7C7F-44BF-8BC7-08325588E60C}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{E5B03EEF-822C-4295-952B-4479AD30082B}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{D3804228-91F4-4502-9595-39584E5A0177}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.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.ActiveCfg = Release|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.Build.0 = Release|Any CPU {BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.Build.0 = Release|Any CPU
{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.ActiveCfg = Debug|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B6269AAC-170A-4346-8B9A-444DED3D9A45}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{1E874C8F-08A2-493B-8421-619F9A6E9E77}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{B47413AA-55D3-49A7-896A-17ADBFF72407}.Release|Any CPU.Build.0 = 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
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -407,6 +867,10 @@ Global
{3FCBCA9C-9DB0-4A96-B47E-30470764CC9C} = {0BB8B634-407A-4610-A91F-11586990767A} {3FCBCA9C-9DB0-4A96-B47E-30470764CC9C} = {0BB8B634-407A-4610-A91F-11586990767A}
{1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {1E874C8F-08A2-493B-8421-619F9A6E9E77} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{B47413AA-55D3-49A7-896A-17ADBFF72407} = {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}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}
+6 -2
View File
@@ -100,7 +100,7 @@ jobs:
- job: Windows_Build_Test - job: Windows_Build_Test
pool: pool:
vmImage: 'windows-2022' vmImage: 'windows-2025'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
@@ -141,9 +141,13 @@ jobs:
dependsOn: Windows_Build_Test dependsOn: Windows_Build_Test
pool: pool:
vmImage: 'windows-2022' vmImage: 'windows-2025'
steps: steps:
- script: |
echo "BuildId = $(buildId)"
displayName: 'Print buildId'
- task: UseDotNet@2 - task: UseDotNet@2
displayName: Use .NET 8.0 displayName: Use .NET 8.0
inputs: inputs:
+1 -1
View File
@@ -1,5 +1,5 @@
pool: pool:
vmImage: 'windows-2022' vmImage: 'windows-2025'
variables: variables:
Prerelease: '' Prerelease: ''
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" /> <Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -18,7 +18,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" /> <PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup>
<ItemGroup>
<None Update="__admin\mappings\*.proto">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="__admin\mappings\*.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>
+22 -7
View File
@@ -4,14 +4,28 @@ var builder = DistributedApplication.CreateBuilder(args);
// IResourceBuilder<ProjectResource> apiService = builder.AddProject<Projects.AspireApp1_ApiService>("apiservice"); // 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 //IResourceBuilder<WireMockServerResource> apiService1 = builder
.AddWireMock("apiservice", WireMockServerArguments.DefaultPort) // //.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) .WithMappingsPath(mappingsPath)
.WithReadStaticMappings()
.WithWatchStaticMappings() .WithWatchStaticMappings()
.WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync); .WithApiMappingBuilder(WeatherForecastApiMock.BuildAsync)
.WithOpenTelemetry(); // Enable OpenTelemetry tracing for Aspire dashboard
//var apiServiceUsedForDocs = builder //var apiServiceUsedForDocs = builder
// .AddWireMock("apiservice1", WireMockServerArguments.DefaultPort) // .AddWireMock("apiservice1", WireMockServerArguments.DefaultPort)
@@ -45,6 +59,7 @@ IResourceBuilder<WireMockServerResource> apiService = builder
builder.AddProject<Projects.AspireApp1_Web>("webfrontend") builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
.WithExternalHttpEndpoints() .WithExternalHttpEndpoints()
.WithReference(apiService); .WithReference(apiService2)
.WaitFor(apiService2);
builder.Build().Run(); await builder.Build().RunAsync();
@@ -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;
}
@@ -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"
}
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" /> <Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -15,7 +15,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" /> <PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -6,4 +6,4 @@ builder.AddProject<Projects.AspireApp1_Web>("webfrontend")
.WithExternalHttpEndpoints() .WithExternalHttpEndpoints()
.WithReference(apiService); .WithReference(apiService);
builder.Build().Run(); await builder.Build().RunAsync();
@@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.5.3" /> <PackageReference Include="xunit" Version="2.5.3" />
@@ -7,7 +7,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -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!");
@@ -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>
@@ -22,7 +22,7 @@ public class MatcherModel
public object? Pattern { get; set; } public object? Pattern { get; set; }
/// <summary> /// <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> /// </summary>
public object[]? Patterns { get; set; } public object[]? Patterns { get; set; }
@@ -24,4 +24,13 @@ public class StatusModel
/// The error message. /// The error message.
/// </summary> /// </summary>
public string? Error { get; set; } 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}]";
}
} }
@@ -1,6 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using WireMock.Validators;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace WireMock.Admin.Mappings; namespace WireMock.Admin.Mappings;
@@ -94,9 +95,14 @@ public partial class RequestModelBuilder
} }
/// <summary> /// <summary>
/// Set the Path. /// Set the Path. Must start with a forward slash (/).
/// </summary> /// </summary>
public RequestModelBuilder WithPath(string value) => WithPath(() => value); public RequestModelBuilder WithPath(string value)
{
PathValidator.ValidateAndThrow(value);
return WithPath(() => value);
}
/// <summary> /// <summary>
/// Set the Path. /// Set the Path.
@@ -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));
}
}
}
@@ -30,7 +30,10 @@
</ItemGroup> </ItemGroup>
<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> </ItemGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'"> <PropertyGroup Condition="'$(Configuration)' == 'Debug - Sonar'">
@@ -42,7 +45,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="9.2.0" /> <PackageReference Include="Aspire.Hosting" Version="13.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -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;
}
}
}
@@ -0,0 +1,12 @@
// Copyright © WireMock.Net
namespace WireMock.Net.Aspire;
internal enum WireMockMappingState
{
NoMappings = 0,
NotSubmitted = 1,
Submitted = 2
}
@@ -1,7 +1,9 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Stef.Validation;
using WireMock.Client.Builders; using WireMock.Client.Builders;
using WireMock.Util;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Aspire.Hosting; namespace Aspire.Hosting;
@@ -21,10 +23,15 @@ public class WireMockServerArguments
private const string DefaultLogger = "WireMockConsoleLogger"; private const string DefaultLogger = "WireMockConsoleLogger";
/// <summary> /// <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. /// If not defined, .NET Aspire automatically assigns a random port.
/// </summary> /// </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> /// <summary>
/// The admin username. /// The admin username.
@@ -67,6 +74,58 @@ public class WireMockServerArguments
/// </summary> /// </summary>
public Func<AdminApiMappingBuilder, CancellationToken, Task>? ApiMappingBuilder { get; set; } 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> /// <summary>
/// Converts the current instance's properties to an array of command-line arguments for starting the WireMock.Net server. /// Converts the current instance's properties to an array of command-line arguments for starting the WireMock.Net server.
/// </summary> /// </summary>
@@ -95,6 +154,25 @@ public class WireMockServerArguments
Add(args, "--WatchStaticMappingsInSubdirectories", "true"); 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 return args
.SelectMany(k => new[] { k.Key, k.Value }) .SelectMany(k => new[] { k.Key, k.Value })
.ToArray(); .ToArray();
@@ -3,10 +3,12 @@
using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle; using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.WireMock; using Aspire.Hosting.WireMock;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks;
using Stef.Validation; using Stef.Validation;
using WireMock.Client.Builders; using WireMock.Client.Builders;
using WireMock.Net.Aspire; using WireMock.Net.Aspire;
using WireMock.Util;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Aspire.Hosting; namespace Aspire.Hosting;
@@ -33,9 +35,31 @@ public static class WireMockServerBuilderExtensions
Guard.NotNullOrWhiteSpace(name); Guard.NotNullOrWhiteSpace(name);
Guard.Condition(port, p => p is null or > 0 and <= ushort.MaxValue); 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); Guard.NotNull(arguments);
var wireMockContainerResource = new WireMockServerResource(name, 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 var resourceBuilder = builder
.AddResource(wireMockContainerResource) .AddResource(wireMockContainerResource)
.WithImage(DefaultLinuxImage) .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 .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(); .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)) if (!string.IsNullOrEmpty(arguments.MappingsPath))
{ {
resourceBuilder = resourceBuilder.WithBindMount(arguments.MappingsPath, DefaultLinuxMappingsPath); 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; 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="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> /// <param name="callback">A callback that allows for setting the <see cref="WireMockServerArguments"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns> /// <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.NotNull(builder);
Guard.NotNullOrWhiteSpace(name); Guard.NotNullOrWhiteSpace(name);
@@ -154,7 +221,7 @@ public static class WireMockServerBuilderExtensions
/// </summary> /// </summary>
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param> /// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</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) public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, Task> configure)
{ {
return wiremock.WithApiMappingBuilder((adminApiMappingBuilder, _) => configure.Invoke(adminApiMappingBuilder)); return wiremock.WithApiMappingBuilder((adminApiMappingBuilder, _) => configure.Invoke(adminApiMappingBuilder));
@@ -165,13 +232,27 @@ public static class WireMockServerBuilderExtensions
/// </summary> /// </summary>
/// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param> /// <param name="wiremock">The <see cref="IResourceBuilder{WireMockServerResource}"/>.</param>
/// <param name="configure">Delegate that will be invoked to configure the WireMock.Net resource.</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) public static IResourceBuilder<WireMockServerResource> WithApiMappingBuilder(this IResourceBuilder<WireMockServerResource> wiremock, Func<AdminApiMappingBuilder, CancellationToken, Task> configure)
{ {
Guard.NotNull(wiremock); Guard.NotNull(wiremock);
wiremock.ApplicationBuilder.Services.TryAddLifecycleHook<WireMockServerLifecycleHook>();
wiremock.Resource.Arguments.ApiMappingBuilder = configure; 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; return wiremock;
} }
@@ -183,11 +264,11 @@ public static class WireMockServerBuilderExtensions
/// dotnet tool install WireMockInspector --global --no-cache --ignore-failed-sources /// dotnet tool install WireMockInspector --global --no-cache --ignore-failed-sources
/// </code> /// </code>
/// </summary> /// </summary>
/// <param name="builder">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param> /// <param name="wiremock">The <see cref="IResourceBuilder{WireMockNetResource}"/>.</param>
/// <returns></returns> /// <returns>A reference to the <see cref="IResourceBuilder{WireMockServerResource}"/>.</returns>
public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> builder) public static IResourceBuilder<WireMockServerResource> WithWireMockInspectorCommand(this IResourceBuilder<WireMockServerResource> wiremock)
{ {
Guard.NotNull(builder); Guard.NotNull(wiremock);
CommandOptions commandOptions = new() CommandOptions commandOptions = new()
{ {
@@ -197,13 +278,47 @@ public static class WireMockServerBuilderExtensions
IconVariant = IconVariant.Filled IconVariant = IconVariant.Filled
}; };
builder.WithCommand( wiremock.WithCommand(
name: "wiremock-inspector", name: "wiremock-inspector",
displayName: "WireMock Inspector", displayName: "WireMock Inspector",
executeCommand: _ => OnRunOpenInspectorCommandAsync(builder), executeCommand: _ => OnRunOpenInspectorCommandAsync(wiremock),
commandOptions: commandOptions); 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) private static Task<ExecuteCommandResult> OnRunOpenInspectorCommandAsync(IResourceBuilder<WireMockServerResource> builder)
@@ -1,5 +1,6 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Diagnostics;
using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle; using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -10,10 +11,15 @@ internal class WireMockServerLifecycleHook(ILoggerFactory loggerFactory) : IDist
{ {
private readonly CancellationTokenSource _shutdownCts = new(); private readonly CancellationTokenSource _shutdownCts = new();
public async Task AfterResourcesCreatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default) private CancellationTokenSource? _linkedCts;
{ private Task? _mappingTask;
var cts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_shutdownCts.Token, cancellationToken);
_mappingTask = Task.Run(async () =>
{
var wireMockServerResources = appModel.Resources var wireMockServerResources = appModel.Resources
.OfType<WireMockServerResource>() .OfType<WireMockServerResource>()
.ToArray(); .ToArray();
@@ -23,19 +29,31 @@ internal class WireMockServerLifecycleHook(ILoggerFactory loggerFactory) : IDist
wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>()); wireMockServerResource.SetLogger(loggerFactory.CreateLogger<WireMockServerResource>());
var endpoint = wireMockServerResource.GetEndpoint(); var endpoint = wireMockServerResource.GetEndpoint();
if (endpoint.IsAllocated) Debug.Assert(endpoint.IsAllocated);
{
await wireMockServerResource.WaitForHealthAsync(cts.Token);
await wireMockServerResource.CallApiMappingBuilderActionAsync(cts.Token); await wireMockServerResource.WaitForHealthAsync(_linkedCts.Token);
wireMockServerResource.StartWatchingStaticMappings(cts.Token); await wireMockServerResource.CallAddProtoDefinitionsAsync(_linkedCts.Token);
}
await wireMockServerResource.CallApiMappingBuilderActionAsync(_linkedCts.Token);
wireMockServerResource.StartWatchingStaticMappings(_linkedCts.Token);
} }
}, _linkedCts.Token);
return Task.CompletedTask;
} }
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
await _shutdownCts.CancelAsync(); await _shutdownCts.CancelAsync();
_linkedCts?.Dispose();
_shutdownCts.Dispose();
if (_mappingTask is not null)
{
await _mappingTask;
}
} }
} }
@@ -5,6 +5,7 @@ using RestEase;
using Stef.Validation; using Stef.Validation;
using WireMock.Client; using WireMock.Client;
using WireMock.Client.Extensions; using WireMock.Client.Extensions;
using WireMock.Net.Aspire;
using WireMock.Util; using WireMock.Util;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
@@ -19,6 +20,7 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
internal WireMockServerArguments Arguments { get; } internal WireMockServerArguments Arguments { get; }
internal Lazy<IWireMockAdminApi> AdminApi => new(CreateWireMockAdminApi); internal Lazy<IWireMockAdminApi> AdminApi => new(CreateWireMockAdminApi);
internal WireMockMappingState ApiMappingState { get; set; } = WireMockMappingState.NoMappings;
private ILogger? _logger; private ILogger? _logger;
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
@@ -64,6 +66,36 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
var mappingBuilder = AdminApi.Value.GetMappingBuilder(); var mappingBuilder = AdminApi.Value.GetMappingBuilder();
await Arguments.ApiMappingBuilder.Invoke(mappingBuilder, cancellationToken); 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) internal void StartWatchingStaticMappings(CancellationToken cancellationToken)
@@ -109,10 +141,17 @@ public class WireMockServerResource : ContainerResource, IResourceWithServiceDis
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) 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 try
{ {
await AdminApi.Value.ReloadStaticMappingsAsync(); var status = await AdminApi.Value.ReloadStaticMappingsAsync(cancellationToken);
_logger?.LogInformation("ReloadStaticMappings called with status: {Status}.", status);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -0,0 +1,60 @@
// 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
{
/// <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 MatchScores.ToScore(Func(bodyAsT));
}
catch (Exception ex)
{
return new MatchResult(ex);
}
}
}
return default;
}
}
@@ -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
@@ -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
}
}
@@ -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
@@ -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";
}
@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
using WireMock.Handlers; using WireMock.Handlers;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Owin.ActivityTracing;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@@ -90,4 +91,12 @@ internal interface IWireMockMiddlewareOptions
QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; } QueryParameterMultipleValueSupport? QueryParameterMultipleValueSupport { get; set; }
public bool ProxyAll { 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
} }
@@ -16,6 +16,10 @@ using System.Collections.Generic;
using WireMock.Constants; using WireMock.Constants;
using WireMock.Exceptions; using WireMock.Exceptions;
using WireMock.Util; using WireMock.Util;
#if ACTIVITY_TRACING_SUPPORTED
using System.Diagnostics;
using WireMock.Owin.ActivityTracing;
#endif
#if !USE_ASPNETCORE #if !USE_ASPNETCORE
using IContext = Microsoft.Owin.IOwinContext; using IContext = Microsoft.Owin.IOwinContext;
using OwinMiddleware = Microsoft.Owin.OwinMiddleware; using OwinMiddleware = Microsoft.Owin.OwinMiddleware;
@@ -97,6 +101,40 @@ namespace WireMock.Owin
{ {
var request = await _requestMapper.MapAsync(ctx.Request, _options).ConfigureAwait(false); 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; var logRequest = false;
IResponseMessage? response = null; IResponseMessage? response = null;
(MappingMatcherResult? Match, MappingMatcherResult? Partial) result = (null, 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}"); _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); response = ResponseMessageBuilder.Create(500, ex.Message);
#if ACTIVITY_TRACING_SUPPORTED
WireMockActivitySource.RecordException(activity, ex);
#endif
} }
finally finally
{ {
@@ -211,6 +253,11 @@ namespace WireMock.Owin
PartialMatchResult = result.Partial?.RequestMatchResult 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); LogRequest(log, logRequest);
try try
@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
using WireMock.Handlers; using WireMock.Handlers;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Owin.ActivityTracing;
using WireMock.Types; using WireMock.Types;
using WireMock.Util; using WireMock.Util;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@@ -106,4 +107,9 @@ internal class WireMockMiddlewareOptions : IWireMockMiddlewareOptions
/// <inheritdoc /> /// <inheritdoc />
public bool ProxyAll { get; set; } public bool ProxyAll { get; set; }
#if ACTIVITY_TRACING_SUPPORTED
/// <inheritdoc />
public ActivityTracingOptions? ActivityTracingOptions { get; set; }
#endif
} }
@@ -2,6 +2,7 @@
using System; using System;
using Stef.Validation; using Stef.Validation;
using WireMock.Owin.ActivityTracing;
using WireMock.Settings; using WireMock.Settings;
namespace WireMock.Owin; namespace WireMock.Owin;
@@ -34,6 +35,27 @@ internal static class WireMockMiddlewareOptionsHelper
options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration; options.RequestLogExpirationDuration = settings.RequestLogExpirationDuration;
options.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; 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) if (settings.CustomCertificateDefined)
{ {
options.X509StoreName = settings.CertificateSettings!.X509StoreName; options.X509StoreName = settings.CertificateSettings!.X509StoreName;
@@ -34,13 +34,6 @@ public partial class Request
return this; 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 /> /// <inheritdoc />
public IRequestBuilder WithBody(IMatcher matcher) public IRequestBuilder WithBody(IMatcher matcher)
{ {
@@ -98,4 +91,20 @@ public partial class Request
_requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func))); _requestMatchers.Add(new RequestMessageBodyMatcher(Guard.NotNull(func)));
return this; 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;
}
} }
@@ -4,6 +4,7 @@ using System;
using Stef.Validation; using Stef.Validation;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.Validators;
namespace WireMock.RequestBuilders; namespace WireMock.RequestBuilders;
@@ -20,7 +21,7 @@ public partial class Request
{ {
Guard.NotNullOrEmpty(matchers); Guard.NotNullOrEmpty(matchers);
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, matchers)); _requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, matchers));
return this; return this;
} }
@@ -34,6 +35,10 @@ public partial class Request
public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths) public IRequestBuilder WithPath(MatchOperator matchOperator, params string[] paths)
{ {
Guard.NotNullOrEmpty(paths); Guard.NotNullOrEmpty(paths);
foreach (var path in paths)
{
PathValidator.ValidateAndThrow(path, nameof(paths));
}
_requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths)); _requestMatchers.Add(new RequestMessagePathMatcher(MatchBehaviour.AcceptOnMatch, matchOperator, paths));
return this; return this;
@@ -29,7 +29,7 @@ public partial class Request : RequestMessageCompositeMatcher, IRequestBuilder
/// <returns>The <see cref="IRequestBuilder"/>.</returns> /// <returns>The <see cref="IRequestBuilder"/>.</returns>
public static IRequestBuilder Create() public static IRequestBuilder Create()
{ {
return new Request(new List<IRequestMatcher>()); return new Request([]);
} }
/// <summary> /// <summary>
@@ -226,14 +226,14 @@ internal class MappingConverter(MatcherMapper mapper)
} }
} }
if (response.Delay is { }) if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
{
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
}
else if (response is { MinimumDelayMilliseconds: > 0, MaximumDelayMilliseconds: > 0 })
{ {
sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})"); sb.AppendLine($" .WithRandomDelay({response.MinimumDelayMilliseconds}, {response.MaximumDelayMilliseconds})");
} }
else if (response.Delay is { })
{
sb.AppendLine($" .WithDelay({response.Delay.Value.TotalMilliseconds})");
}
if (response.UseTransformer) if (response.UseTransformer)
{ {
@@ -412,11 +412,6 @@ public partial class WireMockServer : IWireMockServer
); );
#if USE_ASPNETCORE #if USE_ASPNETCORE
_options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration;
_options.CorsPolicyOptions = _settings.CorsPolicyOptions;
_options.ClientCertificateMode = _settings.ClientCertificateMode;
_options.AcceptAnyClientCertificate = _settings.AcceptAnyClientCertificate;
_httpServer = new AspNetCoreSelfHost(_options, urlOptions); _httpServer = new AspNetCoreSelfHost(_options, urlOptions);
#else #else
_httpServer = new OwinSelfHost(_options, urlOptions); _httpServer = new OwinSelfHost(_options, urlOptions);
@@ -36,7 +36,7 @@ public static class WireMockServerSettingsParser
if (parser.GetBoolSwitchValue("help")) 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; settings = null;
return false; return false;
} }
@@ -85,6 +85,7 @@ public static class WireMockServerSettingsParser
ParseProxyAndRecordSettings(settings, parser); ParseProxyAndRecordSettings(settings, parser);
ParseCertificateSettings(settings, parser); ParseCertificateSettings(settings, parser);
ParseHandlebarsSettings(settings, parser); ParseHandlebarsSettings(settings, parser);
ParseActivityTracingSettings(settings, parser);
return true; return true;
} }
@@ -153,7 +154,7 @@ public static class WireMockServerSettingsParser
} }
else if (settings.HostingScheme is null) else if (settings.HostingScheme is null)
{ {
settings.Urls = parser.GetValues("Urls", ["http://*:9091/"]); settings.Urls = parser.GetValues(nameof(WireMockServerSettings.Urls), defaultValue: ["http://*:9091/"]);
} }
} }
@@ -226,4 +227,19 @@ public static class WireMockServerSettingsParser
}; };
} }
} }
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)
};
}
}
} }
@@ -39,6 +39,8 @@ internal static class WireMockHandlebarsHelpers
#endif #endif
o.CustomHelperPaths = paths; o.CustomHelperPaths = paths;
o.Categories = settings.HandlebarsSettings?.AllowedHandlebarsHelpers ?? HandlebarsSettings.DefaultAllowedHandlebarsHelpers;
o.CustomHelpers = new Dictionary<string, IHelpers>(); o.CustomHelpers = new Dictionary<string, IHelpers>();
if (settings.HandlebarsSettings?.AllowedCustomHandlebarsHelpers.HasFlag(CustomHandlebarsHelpers.File) == true) if (settings.HandlebarsSettings?.AllowedCustomHandlebarsHelpers.HasFlag(CustomHandlebarsHelpers.File) == true)
{ {
+6 -6
View File
@@ -84,22 +84,22 @@ internal static class PortUtils
} }
/// <summary> /// <summary>
/// Extract the isHttps, isHttp2, protocol, host and port from a URL. /// Extract the isHttps, isHttp2, scheme, host and port from a URL.
/// </summary> /// </summary>
public static bool TryExtract(string url, out bool isHttps, out bool isHttp2, [NotNullWhen(true)] out string? protocol, [NotNullWhen(true)] out string? host, out int port) public static bool TryExtract(string url, out bool isHttps, out bool isHttp2, [NotNullWhen(true)] out string? scheme, [NotNullWhen(true)] out string? host, out int port)
{ {
isHttps = false; isHttps = false;
isHttp2 = false; isHttp2 = false;
protocol = null; scheme = null;
host = null; host = null;
port = 0; port = 0;
var match = UrlDetailsRegex.Match(url); var match = UrlDetailsRegex.Match(url);
if (match.Success) if (match.Success)
{ {
protocol = match.Groups["proto"].Value; scheme = match.Groups["proto"].Value;
isHttps = protocol.StartsWith("https", StringComparison.OrdinalIgnoreCase) || protocol.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase); isHttps = scheme.StartsWith("https", StringComparison.OrdinalIgnoreCase) || scheme.StartsWith("grpcs", StringComparison.OrdinalIgnoreCase);
isHttp2 = protocol.StartsWith("grpc", StringComparison.OrdinalIgnoreCase); isHttp2 = scheme.StartsWith("grpc", StringComparison.OrdinalIgnoreCase);
host = match.Groups["host"].Value; host = match.Groups["host"].Value;
return int.TryParse(match.Groups["port"].Value, out port); return int.TryParse(match.Groups["port"].Value, out port);
@@ -52,6 +52,11 @@
<DefineConstants>$(DefineConstants);TRAILINGHEADERS</DefineConstants> <DefineConstants>$(DefineConstants);TRAILINGHEADERS</DefineConstants>
</PropertyGroup> </PropertyGroup>
<!-- Enable Activity tracing support for .NET 5+ where ActivitySource is available -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<DefineConstants>$(DefineConstants);ACTIVITY_TRACING_SUPPORTED</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Matchers\LinqMatcher.cs" /> <Compile Remove="Matchers\LinqMatcher.cs" />
</ItemGroup> </ItemGroup>
@@ -0,0 +1,71 @@
// Copyright © WireMock.Net
using System;
using JsonConverter.Abstractions;
using JsonConverter.Newtonsoft.Json;
using NUnit.Framework;
using WireMock.Admin.Requests;
using WireMock.Logging;
namespace WireMock.Net.NUnit;
/// <summary>
/// When using NUnit, this class enables to log the output from WireMock.Net using the <see cref="TestContext"/>.
/// </summary>
public sealed class TestContextWireMockLogger(IJsonConverter? jsonConverter = null) : IWireMockLogger
{
private readonly JsonConverterOptions _jsonConverterOptions = new() { WriteIndented = true, IgnoreNullValues = true };
private readonly IJsonConverter _jsonConverter = jsonConverter ?? new NewtonsoftJsonConverter();
/// <inheritdoc />
public void Debug(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Debug", formatString, args));
}
/// <inheritdoc />
public void Info(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Info", formatString, args));
}
/// <inheritdoc />
public void Warn(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Warning", formatString, args));
}
/// <inheritdoc />
public void Error(string formatString, params object[] args)
{
TestContext.WriteLine(Format("Error", formatString, args));
}
/// <inheritdoc />
public void Error(string message, Exception exception)
{
TestContext.WriteLine(Format("Error", $"{message} {{0}}", exception));
if (exception is AggregateException ae)
{
ae.Handle(ex =>
{
TestContext.WriteLine(Format("Error", "Exception {0}", ex));
return true;
});
}
}
/// <inheritdoc />
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
var message = _jsonConverter.Serialize(logEntryModel, _jsonConverterOptions);
TestContext.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
}
private static string Format(string level, string formatString, params object[] args)
{
var message = args.Length > 0 ? string.Format(formatString, args) : formatString;
return $"{DateTime.UtcNow} [{level}] : {message}";
}
}
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Some extensions for NUnit</Description>
<AssemblyTitle>WireMock.Net.NUnit</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFrameworks>net462;net6.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>WireMock.Net.NUnit</AssemblyName>
<RootNamespace>WireMock.Net.NUnit</RootNamespace>
<PackageId>WireMock.Net.NUnit</PackageId>
<PackageTags>tdd;wiremock;test;unittest;nunit</PackageTags>
<ProjectGuid>{2DBBD70D-8051-441F-92BB-3F9B8B4B4983}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.8.0" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
</Project>
@@ -5,9 +5,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using WireMock.Net.OpenApiParser.Types; using WireMock.Net.OpenApiParser.Types;
namespace WireMock.Net.OpenApiParser.Extensions; namespace WireMock.Net.OpenApiParser.Extensions;
@@ -6,8 +6,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi;
using Microsoft.OpenApi.Models.Interfaces;
using Newtonsoft.Json; using Newtonsoft.Json;
using Stef.Validation; using Stef.Validation;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
@@ -19,18 +18,12 @@ using SystemTextJsonSerializer = System.Text.Json.JsonSerializer;
namespace WireMock.Net.OpenApiParser.Mappers; namespace WireMock.Net.OpenApiParser.Mappers;
internal class OpenApiPathsMapper internal class OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{ {
private const string HeaderContentType = "Content-Type"; private const string HeaderContentType = "Content-Type";
private readonly WireMockOpenApiParserSettings _settings; private readonly WireMockOpenApiParserSettings _settings = Guard.NotNull(settings);
private readonly ExampleValueGenerator _exampleValueGenerator; private readonly ExampleValueGenerator _exampleValueGenerator = new(settings);
public OpenApiPathsMapper(WireMockOpenApiParserSettings settings)
{
_settings = Guard.NotNull(settings);
_exampleValueGenerator = new ExampleValueGenerator(settings);
}
public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers) public IReadOnlyList<MappingModel> ToMappingModels(OpenApiPaths? paths, IList<OpenApiServer> servers)
{ {
@@ -41,7 +34,7 @@ internal class OpenApiPathsMapper
.ToArray() ?? []; .ToArray() ?? [];
} }
private IReadOnlyList<MappingModel> MapPath(string path, IOpenApiPathItem pathItem, IList<OpenApiServer> servers) private MappingModel[] MapPath(string path, IOpenApiPathItem pathItem, IList<OpenApiServer> servers)
{ {
return pathItem.Operations?.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray() ?? []; return pathItem.Operations?.Select(o => MapOperationToMappingModel(path, o.Key.ToString().ToUpperInvariant(), o.Value, servers)).ToArray() ?? [];
} }
@@ -50,35 +43,7 @@ internal class OpenApiPathsMapper
{ {
var queryParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Query) ?? []; var queryParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Query) ?? [];
var pathParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Path) ?? []; var pathParameters = operation.Parameters?.Where(p => p.In == ParameterLocation.Path) ?? [];
var headers = operation.Parameters?.Where(p => p.In == ParameterLocation.Header) ?? []; var requestHeaders = operation.Parameters?.Where(p => p.In == ParameterLocation.Header) ?? [];
var response = operation.Responses?.FirstOrDefault() ?? new KeyValuePair<string, IOpenApiResponse>();
TryGetContent(response.Value?.Content, out OpenApiMediaType? responseContent, out var responseContentType);
var responseSchema = response.Value?.Content?.FirstOrDefault().Value?.Schema;
var responseExample = responseContent?.Example;
var responseSchemaExample = responseContent?.Schema?.Example;
var responseBody = responseExample ?? responseSchemaExample ?? MapSchemaToObject(responseSchema);
var requestBodyModel = new BodyModel();
if (operation.RequestBody != null && operation.RequestBody.Content != null && operation.RequestBody.Required)
{
var request = operation.RequestBody.Content;
TryGetContent(request, out var requestContent, out _);
var requestBodySchema = operation.RequestBody.Content.First().Value?.Schema;
var requestBodyExample = requestContent!.Example;
var requestBodySchemaExample = requestContent.Schema?.Example;
var requestBodyMapped = requestBodyExample ?? requestBodySchemaExample ?? MapSchemaToObject(requestBodySchema);
requestBodyModel = MapRequestBody(requestBodyMapped);
}
if (!int.TryParse(response.Key, out var httpStatusCode))
{
httpStatusCode = 200;
}
return new MappingModel return new MappingModel
{ {
@@ -88,15 +53,94 @@ internal class OpenApiPathsMapper
Methods = [httpMethod], Methods = [httpMethod],
Path = PathUtils.Combine(MapBasePath(servers), MapPathWithParameters(path, pathParameters)), Path = PathUtils.Combine(MapBasePath(servers), MapPathWithParameters(path, pathParameters)),
Params = MapQueryParameters(queryParameters), Params = MapQueryParameters(queryParameters),
Headers = MapRequestHeaders(headers), Headers = MapRequestHeaders(requestHeaders),
Body = requestBodyModel Body = GetRequestBodyModel(operation.RequestBody)
}, },
Response = new ResponseModel Response = GetResponseModel(operation.Responses?.FirstOrDefault())
{ };
StatusCode = httpStatusCode,
Headers = MapHeaders(responseContentType, response.Value?.Headers),
BodyAsJson = responseBody != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(responseBody)) : null
} }
private BodyModel GetRequestBodyModel(IOpenApiRequestBody? openApiRequestBody)
{
if (openApiRequestBody is not { Content: not null, Required: true })
{
return new BodyModel();
}
var content = openApiRequestBody.Content;
TryGetContent(content, out var requestContent, out _);
var requestExample = requestContent?.Example;
var requestExamples = requestContent?.Examples;
var requestSchemaExample = requestContent?.Schema?.Example;
var requestSchemaExamples = requestContent?.Schema?.Examples;
JsonNode? request;
if (requestExample != null)
{
request = requestExample;
}
else if (requestSchemaExample != null)
{
request = requestSchemaExample;
}
else if (requestExamples != null)
{
request = requestExamples.FirstOrDefault().Value.Value;
}
else if (requestSchemaExamples != null)
{
request = requestSchemaExamples.FirstOrDefault();
}
else
{
var requestSchema = content?.FirstOrDefault().Value.Schema;
request = MapSchemaToObject(requestSchema);
}
return MapRequestBody(request) ?? new BodyModel();
}
private ResponseModel GetResponseModel(KeyValuePair<string, IOpenApiResponse>? openApiResponse)
{
var content = openApiResponse?.Value.Content;
TryGetContent(content, out var responseContent, out var contentType);
var responseExample = responseContent?.Example;
var responseExamples = responseContent?.Examples;
var responseSchemaExample = responseContent?.Schema?.Example;
var responseSchemaExamples = responseContent?.Schema?.Examples;
JsonNode? response;
if (responseExample != null)
{
response = responseExample;
}
else if (responseSchemaExample != null)
{
response = responseSchemaExample;
}
else if (responseExamples != null)
{
response = responseExamples.FirstOrDefault().Value.Value;
}
else if (responseSchemaExamples != null)
{
response = responseSchemaExamples.FirstOrDefault();
}
else
{
var responseSchema = content?.FirstOrDefault().Value?.Schema;
response = MapSchemaToObject(responseSchema);
}
return new ResponseModel
{
StatusCode = int.TryParse(openApiResponse?.Key, out var httpStatusCode) ? httpStatusCode : 200,
Headers = MapHeaders(contentType, openApiResponse?.Value.Headers),
BodyAsJson = response != null ? JsonConvert.DeserializeObject(SystemTextJsonSerializer.Serialize(response)) : null
}; };
} }
@@ -1,7 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi;
namespace WireMock.Net.OpenApiParser.Settings; namespace WireMock.Net.OpenApiParser.Settings;
@@ -1,7 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using Microsoft.OpenApi.Models.Interfaces; using Microsoft.OpenApi;
using RandomDataGenerator.FieldOptions; using RandomDataGenerator.FieldOptions;
using RandomDataGenerator.Randomizers; using RandomDataGenerator.Randomizers;
@@ -1,8 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi;
using Microsoft.OpenApi.Models.Interfaces;
namespace WireMock.Net.OpenApiParser.Settings; namespace WireMock.Net.OpenApiParser.Settings;
@@ -3,8 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi;
using Microsoft.OpenApi.Models.Interfaces;
using Stef.Validation; using Stef.Validation;
using WireMock.Net.OpenApiParser.Extensions; using WireMock.Net.OpenApiParser.Extensions;
using WireMock.Net.OpenApiParser.Settings; using WireMock.Net.OpenApiParser.Settings;
@@ -27,22 +27,22 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.8.0" /> <PackageReference Include="RamlToOpenApiConverter.SourceOnly" Version="0.11.0" />
<PackageReference Include="YamlDotNet" Version="8.1.0" /> <PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="RandomDataGenerator.Net" Version="1.0.19" /> <PackageReference Include="RandomDataGenerator.Net" Version="1.0.19" />
<PackageReference Include="Stef.Validation" Version="0.1.1" /> <PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'"> <ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.0.0-preview.17" /> <PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'"> <ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.40" PrivateAssets="All" /> <PackageReference Include="ILRepack.Lib.MSBuild.Task" Version="2.0.40" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi" Version="2.0.0-preview.17" PrivateAssets="All" /> <PackageReference Include="Microsoft.OpenApi" Version="2.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.0.0-preview.17" PrivateAssets="All" /> <PackageReference Include="Microsoft.OpenApi.YamlReader" Version="2.3.0" PrivateAssets="All" />
<PackageReference Include="System.Text.Json" Version="8.0.5" /> <PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="SharpYaml" Version="2.1.1" /> <PackageReference Include="SharpYaml" Version="2.1.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi;
using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.YamlReader; using Microsoft.OpenApi.YamlReader;
using RamlToOpenApiConverter; using RamlToOpenApiConverter;
@@ -0,0 +1,35 @@
// Copyright © WireMock.Net
using JetBrains.Annotations;
namespace WireMock.OpenTelemetry;
/// <summary>
/// OpenTelemetry exporter configuration options for WireMock.Net.
/// These options control the export of traces to an OTLP endpoint.
/// For controlling what data is recorded in traces, configure ActivityTracingOptions in WireMockServerSettings.
/// </summary>
public class OpenTelemetryOptions
{
/// <summary>
/// Gets or sets a value indicating whether to exclude admin interface requests from ASP.NET Core instrumentation.
/// Default is <c>true</c>.
/// </summary>
/// <remarks>
/// This controls the ASP.NET Core HTTP server instrumentation filter.
/// To also exclude admin requests from WireMock's own activity tracing,
/// set <c>ActivityTracingOptions.ExcludeAdminRequests</c> in WireMockServerSettings.
/// </remarks>
[PublicAPI]
public bool ExcludeAdminRequests { get; set; } = true;
/// <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>
[PublicAPI]
public string? OtlpExporterEndpoint { get; set; }
}
@@ -0,0 +1,44 @@
// Copyright © WireMock.Net
using System.Collections;
using Stef.Validation;
using WireMock.Settings;
namespace WireMock.OpenTelemetry;
/// <summary>
/// A static helper class to parse commandline arguments into OpenTelemetryOptions.
/// </summary>
public static class OpenTelemetryOptionsParser
{
private const string Prefix = "OpenTelemetry";
/// <summary>
/// Parse commandline arguments into OpenTelemetryOptions.
/// </summary>
/// <param name="args">The commandline arguments</param>
/// <param name="environment">The environment settings (optional)</param>
/// <param name="options">The parsed options, or null if OpenTelemetry is not enabled</param>
/// <returns>Always returns true.</returns>
public static bool TryParseArguments(string[] args, IDictionary? environment, out OpenTelemetryOptions? options)
{
Guard.HasNoNulls(args);
var parser = new SimpleSettingsParser();
parser.Parse(args, environment);
if (!parser.GetBoolValue($"{Prefix}Enabled"))
{
options = null;
return true;
}
options = new OpenTelemetryOptions
{
ExcludeAdminRequests = parser.GetBoolValue($"{Prefix}ExcludeAdminRequests", defaultValue: true),
OtlpExporterEndpoint = parser.GetStringValue($"{Prefix}OtlpExporterEndpoint")
};
return true;
}
}
+160
View File
@@ -0,0 +1,160 @@
# WireMock.Net.OpenTelemetry
OpenTelemetry tracing support for WireMock.Net. This package provides instrumentation and OTLP (OpenTelemetry Protocol) exporting capabilities.
## Overview
WireMock.Net automatically creates `System.Diagnostics.Activity` objects for request tracing when `ActivityTracingOptions` is configured (not null). These activities use the built-in .NET distributed tracing infrastructure and are available without any additional dependencies.
This package provides:
- **WireMock.Net instrumentation** - Adds the WireMock.Net ActivitySource to the tracing pipeline
- **ASP.NET Core instrumentation** - Standard HTTP server tracing with request filtering
- **OTLP exporter** - Sends traces to an OpenTelemetry collector
## Installation
```bash
dotnet add package WireMock.Net.OpenTelemetry
```
## Usage
### Option 1: Using AdditionalServiceRegistration (Recommended)
```csharp
using WireMock.OpenTelemetry;
using WireMock.Server;
using WireMock.Settings;
var openTelemetryOptions = new OpenTelemetryOptions
{
ExcludeAdminRequests = true,
OtlpExporterEndpoint = "http://localhost:4317" // Your OTEL collector
};
var settings = new WireMockServerSettings
{
// Setting ActivityTracingOptions (not null) enables activity creation in middleware
ActivityTracingOptions = new ActivityTracingOptions
{
ExcludeAdminRequests = true,
RecordRequestBody = false, // PII concern
RecordResponseBody = false, // PII concern
RecordMatchDetails = true
},
AdditionalServiceRegistration = services =>
{
services.AddWireMockOpenTelemetry(openTelemetryOptions);
}
};
var server = WireMockServer.Start(settings);
```
### Option 2: Custom TracerProvider Configuration
For more control over the tracing configuration:
```csharp
using OpenTelemetry;
using OpenTelemetry.Trace;
using WireMock.OpenTelemetry;
var openTelemetryOptions = new OpenTelemetryOptions();
// Configure your own TracerProvider
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddWireMockInstrumentation(openTelemetryOptions) // Adds WireMock.Net source
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4317");
})
.Build();
```
## Extension Methods
### `AddWireMockOpenTelemetry`
Adds full OpenTelemetry tracing to the service collection with instrumentation and OTLP exporter:
```csharp
services.AddWireMockOpenTelemetry(openTelemetryOptions);
```
This configures:
- The WireMock.Net ActivitySource
- ASP.NET Core instrumentation
- OTLP exporter (using the endpoint from `OpenTelemetryOptions.OtlpExporterEndpoint` or the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable)
### `AddWireMockInstrumentation`
Adds WireMock instrumentation to an existing TracerProviderBuilder:
```csharp
tracerProvider.AddWireMockInstrumentation(openTelemetryOptions);
```
## Configuration
### OpenTelemetryOptions (Exporter configuration)
| Property | Description | Default |
|----------|-------------|---------|
| `ExcludeAdminRequests` | Exclude `/__admin/*` from ASP.NET Core instrumentation | `true` |
| `OtlpExporterEndpoint` | OTLP collector endpoint URL | Uses `OTEL_EXPORTER_OTLP_ENDPOINT` env var |
### ActivityTracingOptions (Trace content configuration)
Configured in `WireMockServerSettings.ActivityTracingOptions`:
| Property | Description | Default |
|----------|-------------|---------|
| `ExcludeAdminRequests` | Exclude `/__admin/*` from WireMock activity creation | `true` |
| `RecordRequestBody` | Include request body in trace attributes | `false` |
| `RecordResponseBody` | Include response body in trace attributes | `false` |
| `RecordMatchDetails` | Include mapping match details in trace attributes | `true` |
## Trace Attributes
WireMock.Net traces include these semantic conventions:
**Standard HTTP attributes:**
- `http.request.method`
- `url.full`
- `url.path`
- `server.address`
- `http.response.status_code`
- `client.address`
**WireMock-specific attributes:**
- `wiremock.mapping.matched` - Whether a mapping was found
- `wiremock.mapping.guid` - GUID of the matched mapping
- `wiremock.mapping.title` - Title of the matched mapping
- `wiremock.match.score` - Match score
- `wiremock.request.guid` - GUID of the request
## CLI Arguments
When using WireMock.Net.StandAlone or Docker images, activity tracing and OpenTelemetry can be configured via command-line arguments:
**Activity Tracing (what gets recorded):**
```bash
--ActivityTracingEnabled true
--ActivityTracingExcludeAdminRequests true
--ActivityTracingRecordRequestBody false
--ActivityTracingRecordResponseBody false
--ActivityTracingRecordMatchDetails true
```
**OpenTelemetry Export (where traces are sent):**
```bash
--OpenTelemetryEnabled true
--OpenTelemetryOtlpExporterEndpoint http://localhost:4317
--OpenTelemetryExcludeAdminRequests true
```
## Requirements
- .NET 6.0 or later
- WireMock.Net 1.6.0 or later
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--<Version>0.0.1-preview-01</Version>-->
<Description>OpenTelemetry exporter support for WireMock.Net</Description>
<AssemblyTitle>WireMock.Net.OpenTelemetry</AssemblyTitle>
<Authors>Petr Houška</Authors>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<RootNamespace>WireMock.OpenTelemetry</RootNamespace>
<PackageTags>wiremock;opentelemetry;otel;tracing;telemetry</PackageTags>
<ProjectGuid>{C8F4E6D2-9A3B-4F1C-8D5E-7A2B3C4D5E6F}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<CodeAnalysisRuleSet>../WireMock.Net/WireMock.Net.ruleset</CodeAnalysisRuleSet>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<!-- OpenTelemetry packages -->
<ItemGroup>
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.14.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Shared\WireMock.Net.Shared.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,114 @@
// Copyright © WireMock.Net
using System;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace WireMock.OpenTelemetry;
/// <summary>
/// Extension methods for configuring OpenTelemetry tracing for WireMock.Net.
/// </summary>
public static class WireMockOpenTelemetryExtensions
{
private const string ServiceName = "WireMock.Net";
private const string WireMockActivitySourceName = "WireMock.Net";
/// <summary>
/// Adds OpenTelemetry tracing to the WireMock server with instrumentation and OTLP exporter.
/// This configures:
/// - WireMock.Net ActivitySource instrumentation (custom WireMock traces with mapping details)
/// - ASP.NET Core instrumentation (standard HTTP server traces)
/// - OTLP exporter to send traces to a collector
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="options">The OpenTelemetry options containing exporter configuration.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddWireMockOpenTelemetry(
this IServiceCollection services,
OpenTelemetryOptions? options)
{
if (options is null)
{
return services;
}
services.AddOpenTelemetry()
.ConfigureResource(resource =>
{
resource.AddService(
serviceName: ServiceName,
serviceVersion: typeof(WireMockOpenTelemetryExtensions).Assembly.GetName().Version?.ToString() ?? "unknown"
);
})
.WithTracing(tracing =>
{
// Add WireMock-specific traces
tracing.AddSource(WireMockActivitySourceName);
// Add ASP.NET Core instrumentation for standard HTTP server traces
tracing.AddAspNetCoreInstrumentation(aspNetOptions =>
{
// Filter out admin requests if configured
if (options.ExcludeAdminRequests)
{
aspNetOptions.Filter = context =>
{
var path = context.Request.Path.Value ?? string.Empty;
return !path.StartsWith("/__admin", StringComparison.OrdinalIgnoreCase);
};
}
});
// Add OTLP exporter - automatically reads OTEL_EXPORTER_OTLP_ENDPOINT from environment
// If explicit endpoint is specified in options, use that instead
var otlpEndpoint = options.OtlpExporterEndpoint;
if (!string.IsNullOrEmpty(otlpEndpoint))
{
tracing.AddOtlpExporter(exporterOptions =>
{
exporterOptions.Endpoint = new Uri(otlpEndpoint);
});
}
else
{
// Use default - reads from OTEL_EXPORTER_OTLP_ENDPOINT env var
tracing.AddOtlpExporter();
}
});
return services;
}
/// <summary>
/// Configures OpenTelemetry tracing builder with WireMock.Net ActivitySource and ASP.NET Core instrumentation.
/// Use this method when you want more control over the TracerProvider configuration.
/// </summary>
/// <param name="tracing">The TracerProviderBuilder to configure.</param>
/// <param name="options">The OpenTelemetry options (optional).</param>
/// <returns>The TracerProviderBuilder for chaining.</returns>
public static TracerProviderBuilder AddWireMockInstrumentation(
this TracerProviderBuilder tracing,
OpenTelemetryOptions? options = null)
{
// Add WireMock-specific traces
tracing.AddSource(WireMockActivitySourceName);
// Add ASP.NET Core instrumentation for standard HTTP server traces
tracing.AddAspNetCoreInstrumentation(aspNetOptions =>
{
// Filter out admin requests if configured
if (options?.ExcludeAdminRequests == true)
{
aspNetOptions.Filter = context =>
{
var path = context.Request.Path.Value ?? string.Empty;
return !path.StartsWith("/__admin", StringComparison.OrdinalIgnoreCase);
};
}
});
return tracing;
}
}
@@ -103,7 +103,7 @@ public class ProtoBufMatcher : IProtoBufMatcher
try try
{ {
return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken).ConfigureAwait(false); return await ProtoBufToJsonConverter.ConvertAsync(request, cancellationToken);
} }
catch catch
{ {
@@ -26,7 +26,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ProtoBufJsonConverter" Version="0.10.0" /> <PackageReference Include="ProtoBufJsonConverter" Version="0.11.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WireMock.Net.GraphQL, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] [assembly: InternalsVisibleTo("WireMock.Net.GraphQL, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.ProtoBuf, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] [assembly: InternalsVisibleTo("WireMock.Net.ProtoBuf, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] [assembly: InternalsVisibleTo("WireMock.Net.Matchers.CSharpCode, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.OpenTelemetry, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
// [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] // [assembly: InternalsVisibleTo("WireMock.Net.StandAlone, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
[assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")] [assembly: InternalsVisibleTo("WireMock.Net.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e138ec44d93acac565953052636eb8d5e7e9f27ddb030590055cd1a0ab2069a5623f1f77ca907d78e0b37066ca0f6d63da7eecc3fcb65b76aa8ebeccf7ebe1d11264b8404cd9b1cbbf2c83f566e033b3e54129f6ef28daffff776ba7aebbc53c0d635ebad8f45f78eb3f7e0459023c218f003416e080f96a1a3c5ffeb56bee9e")]
@@ -80,6 +80,14 @@ public interface IBodyRequestBuilder : IMultiPartRequestBuilder
/// <returns>The <see cref="IRequestBuilder"/>.</returns> /// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBody(Func<object?, bool> func); IRequestBuilder WithBody(Func<object?, bool> func);
/// <summary>
/// WithBody: func (type)
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="func">The function.</param>
/// <returns>The <see cref="IRequestBuilder"/>.</returns>
IRequestBuilder WithBodyAsType<T>(Func<T?, bool> func);
/// <summary> /// <summary>
/// WithBody: func (BodyData object) /// WithBody: func (BodyData object)
/// </summary> /// </summary>
@@ -0,0 +1,42 @@
// Copyright © WireMock.Net
using JetBrains.Annotations;
namespace WireMock.Settings;
/// <summary>
/// Options for controlling activity tracing in WireMock.Net.
/// These options control the creation of System.Diagnostics.Activity objects
/// but do not require any OpenTelemetry exporter dependencies.
/// </summary>
/// <remarks>
/// To export traces to an OpenTelemetry collector, install the WireMock.Net.OpenTelemetry package
/// and configure the exporter using the provided extension methods.
/// </remarks>
[PublicAPI]
public class ActivityTracingOptions
{
/// <summary>
/// Gets or sets a value indicating whether to exclude admin interface requests from activity 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;
}
@@ -55,7 +55,7 @@ internal class SimpleSettingsParser
// Now also parse environment // Now also parse environment
if (environment != null) if (environment != null)
{ {
foreach (string key in environment.Keys) foreach (var key in environment.Keys.OfType<string>())
{ {
if (key.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) && environment.TryGetStringValue(key, out var value)) if (key.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) && environment.TryGetStringValue(key, out var value))
{ {
@@ -104,6 +104,21 @@ internal class SimpleSettingsParser
}, defaultValue); }, defaultValue);
} }
public bool GetBoolWithDefault(string key1, string key2, bool defaultValue)
{
if (Contains(key1))
{
return GetBoolValue(key1);
}
if (Contains(key2))
{
return GetBoolValue(key2);
}
return defaultValue;
}
public bool GetBoolSwitchValue(string name) public bool GetBoolSwitchValue(string name)
{ {
return Contains(name); return Contains(name);
@@ -338,4 +338,15 @@ public class WireMockServerSettings
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public HandlebarsSettings? HandlebarsSettings { get; set; } public HandlebarsSettings? HandlebarsSettings { get; set; }
/// <summary>
/// Gets or sets the activity tracing options.
/// When set (not null), WireMock.Net will create System.Diagnostics.Activity objects for request processing.
/// </summary>
/// <remarks>
/// To export traces to an OpenTelemetry collector, install the WireMock.Net.OpenTelemetry package
/// and configure the exporter using the provided extension methods.
/// </remarks>
[PublicAPI]
public ActivityTracingOptions? ActivityTracingOptions { get; set; }
} }
@@ -10,6 +10,9 @@ using WireMock.Exceptions;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; using WireMock.Settings;
#if OPENTELEMETRY_SUPPORTED
using WireMock.OpenTelemetry;
#endif
namespace WireMock.Net.StandAlone; namespace WireMock.Net.StandAlone;
@@ -37,6 +40,39 @@ public static class StandAloneApp
return server; return server;
} }
#if OPENTELEMETRY_SUPPORTED
/// <summary>
/// Start WireMock.Net standalone Server based on the WireMockServerSettings with OpenTelemetry tracing.
/// </summary>
/// <param name="settings">The WireMockServerSettings</param>
/// <param name="openTelemetryOptions">The OpenTelemetry options for exporting traces.</param>
[PublicAPI]
public static WireMockServer Start(WireMockServerSettings settings, OpenTelemetryOptions? openTelemetryOptions)
{
Guard.NotNull(settings);
// Wire up OpenTelemetry OTLP exporter if options are provided
if (openTelemetryOptions is not null)
{
// Enable activity tracing in settings so middleware creates activities
// Only set ExcludeAdminRequests if not already configured
settings.ActivityTracingOptions ??= new ActivityTracingOptions
{
ExcludeAdminRequests = openTelemetryOptions.ExcludeAdminRequests
};
var existingRegistration = settings.AdditionalServiceRegistration;
settings.AdditionalServiceRegistration = services =>
{
existingRegistration?.Invoke(services);
services.AddWireMockOpenTelemetry(openTelemetryOptions);
};
}
return Start(settings);
}
#endif
/// <summary> /// <summary>
/// Start WireMock.Net standalone Server based on the commandline arguments. /// Start WireMock.Net standalone Server based on the commandline arguments.
/// </summary> /// </summary>
@@ -71,7 +107,13 @@ public static class StandAloneApp
settings.Logger?.Info("Version [{0}]", Version); settings.Logger?.Info("Version [{0}]", Version);
settings.Logger?.Debug("Server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'"))); settings.Logger?.Debug("Server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'")));
#if OPENTELEMETRY_SUPPORTED
// Parse OpenTelemetry options separately using the OTEL project parser
OpenTelemetryOptionsParser.TryParseArguments(args, Environment.GetEnvironmentVariables(), out var openTelemetryOptions);
server = Start(settings, openTelemetryOptions);
#else
server = Start(settings); server = Start(settings);
#endif
return true; return true;
} }
@@ -35,6 +35,11 @@
<DefineConstants>USE_ASPNETCORE;NET46</DefineConstants> <DefineConstants>USE_ASPNETCORE;NET46</DefineConstants>
</PropertyGroup> </PropertyGroup>
<!-- Enable OpenTelemetry exporter support for .NET 6+ -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<DefineConstants>$(DefineConstants);OPENTELEMETRY_SUPPORTED</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="PolySharp" Version="1.15.0"> <PackageReference Include="PolySharp" Version="1.15.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -42,4 +47,9 @@
</PackageReference> </PackageReference>
<ProjectReference Include="..\WireMock.Net\WireMock.Net.csproj" /> <ProjectReference Include="..\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup> </ItemGroup>
<!-- OpenTelemetry exporter for .NET 6+ -->
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<ProjectReference Include="..\WireMock.Net.OpenTelemetry\WireMock.Net.OpenTelemetry.csproj" />
</ItemGroup>
</Project> </Project>
@@ -0,0 +1,18 @@
// Copyright © WireMock.Net
using WireMock.Net.Testcontainers;
namespace DotNet.Testcontainers.Configurations;
internal static class HttpWaitStrategyExtensions
{
internal static HttpWaitStrategy WithBasicAuthentication(this HttpWaitStrategy strategy, WireMockConfiguration configuration)
{
if (configuration.HasBasicAuthentication)
{
return strategy.WithBasicAuthentication(configuration.Username, configuration.Password);
}
return strategy;
}
}
@@ -0,0 +1,20 @@
// Copyright © WireMock.Net
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Net.Testcontainers.Utils;
internal static class CombineUtils
{
internal static List<T> Combine<T>(List<T> oldValue, List<T> newValue)
{
return oldValue.Union(newValue).ToList();
}
internal static Dictionary<TKey, TValue> Combine<TKey, TValue>(Dictionary<TKey, TValue> oldValue, Dictionary<TKey, TValue> newValue)
where TKey : notnull
{
return oldValue.Union(newValue).ToDictionary(item => item.Key, item => item.Value);
}
}
@@ -39,7 +39,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" /> <PackageReference Include="Stef.Validation" Version="0.1.1" />
<PackageReference Include="Testcontainers" Version="4.8.0" /> <PackageReference Include="Testcontainers" Version="4.10.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,12 +1,12 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Docker.DotNet.Models; using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Configurations;
using JetBrains.Annotations; using JetBrains.Annotations;
using Stef.Validation; using Stef.Validation;
using WireMock.Net.Testcontainers.Utils;
namespace WireMock.Net.Testcontainers; namespace WireMock.Net.Testcontainers;
@@ -77,8 +77,8 @@ public sealed class WireMockConfiguration : ContainerConfiguration
StaticMappingsPath = BuildConfiguration.Combine(oldValue.StaticMappingsPath, newValue.StaticMappingsPath); StaticMappingsPath = BuildConfiguration.Combine(oldValue.StaticMappingsPath, newValue.StaticMappingsPath);
WatchStaticMappings = BuildConfiguration.Combine(oldValue.WatchStaticMappings, newValue.WatchStaticMappings); WatchStaticMappings = BuildConfiguration.Combine(oldValue.WatchStaticMappings, newValue.WatchStaticMappings);
WatchStaticMappingsInSubdirectories = BuildConfiguration.Combine(oldValue.WatchStaticMappingsInSubdirectories, newValue.WatchStaticMappingsInSubdirectories); WatchStaticMappingsInSubdirectories = BuildConfiguration.Combine(oldValue.WatchStaticMappingsInSubdirectories, newValue.WatchStaticMappingsInSubdirectories);
AdditionalUrls = Combine(oldValue.AdditionalUrls, newValue.AdditionalUrls); AdditionalUrls = CombineUtils.Combine(oldValue.AdditionalUrls, newValue.AdditionalUrls);
ProtoDefinitions = Combine(oldValue.ProtoDefinitions, newValue.ProtoDefinitions); ProtoDefinitions = CombineUtils.Combine(oldValue.ProtoDefinitions, newValue.ProtoDefinitions);
} }
/// <summary> /// <summary>
@@ -130,16 +130,4 @@ public sealed class WireMockConfiguration : ContainerConfiguration
return this; return this;
} }
private static List<T> Combine<T>(List<T> oldValue, List<T> newValue)
{
return oldValue.Concat(newValue).ToList();
}
private static Dictionary<TKey, TValue> Combine<TKey, TValue>(Dictionary<TKey, TValue> oldValue, Dictionary<TKey, TValue> newValue)
{
return newValue
.Concat(oldValue.Where(item => !newValue.Keys.Contains(item.Key)))
.ToDictionary(item => item.Key, item => item.Value);
}
} }
@@ -24,28 +24,21 @@ namespace WireMock.Net.Testcontainers;
/// <summary> /// <summary>
/// A container for running WireMock in a docker environment. /// A container for running WireMock in a docker environment.
/// </summary> /// </summary>
public sealed class WireMockContainer : DockerContainer /// <remarks>
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
/// </remarks>
/// <param name="configuration">The container configuration.</param>
public sealed class WireMockContainer(WireMockConfiguration configuration) : DockerContainer(configuration)
{ {
private const int EnhancedFileSystemWatcherTimeoutMs = 2000; private const int EnhancedFileSystemWatcherTimeoutMs = 2000;
internal const int ContainerPort = 80; internal const int ContainerPort = 80;
private readonly WireMockConfiguration _configuration; private readonly WireMockConfiguration _configuration = Guard.NotNull(configuration);
private IWireMockAdminApi? _adminApi; private IWireMockAdminApi? _adminApi;
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher; private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
private IDictionary<int, Uri>? _publicUris; private IDictionary<int, Uri>? _publicUris;
/// <summary>
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public WireMockContainer(WireMockConfiguration configuration) : base(configuration)
{
_configuration = Guard.NotNull(configuration);
Started += async (sender, eventArgs) => await WireMockContainerStartedAsync(sender, eventArgs);
}
/// <summary> /// <summary>
/// Gets the public Url. /// Gets the public Url.
/// </summary> /// </summary>
@@ -156,14 +149,29 @@ public sealed class WireMockContainer : DockerContainer
try try
{ {
await _adminApi.ReloadStaticMappingsAsync(cancellationToken); var result = await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
Logger.LogInformation("WireMock.Net -> ReloadStaticMappings result: {Result}", result);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings"); Logger.LogWarning(ex, "WireMock.Net -> Error calling /__admin/mappings/reloadStaticMappings");
} }
} }
/// <summary>
/// Performs additional actions after the container is ready.
/// </summary>
public Task CallAdditionalActionsAfterReadyAsync()
{
Logger.LogInformation("WireMock.Net -> Calling additional actions.");
_adminApi = CreateWireMockAdminClient();
RegisterEnhancedFileSystemWatcher();
return AddProtoDefinitionsAsync();
}
/// <inheritdoc /> /// <inheritdoc />
protected override ValueTask DisposeAsyncCore() protected override ValueTask DisposeAsyncCore()
{ {
@@ -196,15 +204,6 @@ public sealed class WireMockContainer : DockerContainer
} }
} }
private async Task WireMockContainerStartedAsync(object sender, EventArgs e)
{
_adminApi = CreateWireMockAdminClient();
RegisterEnhancedFileSystemWatcher();
await CallAdditionalActionsAfterStartedAsync();
}
private void RegisterEnhancedFileSystemWatcher() private void RegisterEnhancedFileSystemWatcher()
{ {
if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath)) if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath))
@@ -222,23 +221,31 @@ public sealed class WireMockContainer : DockerContainer
_enhancedFileSystemWatcher.EnableRaisingEvents = true; _enhancedFileSystemWatcher.EnableRaisingEvents = true;
} }
private async Task CallAdditionalActionsAfterStartedAsync() private async Task AddProtoDefinitionsAsync()
{ {
foreach (var kvp in _configuration.ProtoDefinitions) foreach (var kvp in _configuration.ProtoDefinitions)
{ {
Logger.LogInformation("Adding ProtoDefinition {Id}", kvp.Key); Logger.LogInformation("WireMock.Net -> Adding ProtoDefinition '{Id}'", kvp.Key);
foreach (var protoDefinition in kvp.Value) foreach (var protoDefinition in kvp.Value)
{ {
try try
{ {
await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition); var result = await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
Logger.LogInformation("WireMock.Net -> AddProtoDefinition '{Id}' result: {Result}", kvp.Key, result);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", kvp.Key); Logger.LogWarning(ex, "WireMock.Net -> Error adding ProtoDefinition '{Id}'.", kvp.Key);
} }
} }
} }
// Force a reload of static mappings when ProtoDefinitions are added at server-level to fix #1382
if (_configuration.ProtoDefinitions.Count > 0)
{
await ReloadStaticMappingsAsync();
}
} }
private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args) private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArgs args)
@@ -246,16 +253,17 @@ public sealed class WireMockContainer : DockerContainer
try try
{ {
await ReloadStaticMappingsAsync(args.FullPath); await ReloadStaticMappingsAsync(args.FullPath);
Logger.LogInformation("WireMock.Net -> ReloadStaticMappings triggered from file change: '{FullPath}'.", args.FullPath);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Error reloading static mappings from '{FullPath}'.", args.FullPath); Logger.LogWarning(ex, "WireMock.Net -> Error reloading static mappings from '{FullPath}'.", args.FullPath);
} }
} }
private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default) private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default)
{ {
Logger.LogInformation("MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path); Logger.LogInformation("WireMock.Net -> MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path);
await ReloadStaticMappingsAsync(cancellationToken); await ReloadStaticMappingsAsync(cancellationToken);
} }
@@ -2,6 +2,8 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Docker.DotNet.Models; using Docker.DotNet.Models;
using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Builders;
@@ -62,6 +64,29 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
return WithImage(OSPlatform.Windows); return WithImage(OSPlatform.Windows);
} }
/// <summary>
/// Sets a custom WireMock.Net image for which to create the container.
/// </summary>
/// <param name="image">The image name.</param>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public new WireMockContainerBuilder WithImage(string image)
{
return WithCustomImage(image);
}
/// <summary>
/// Sets a custom WireMock.Net image for which to create the container.
/// </summary>
/// <param name="image">The image name.</param>
/// <returns>A configured instance of <see cref="WireMockContainerBuilder"/></returns>
[PublicAPI]
public WireMockContainerBuilder WithCustomImage(string image)
{
_imageOS ??= TestcontainersUtils.GetImageOSAsync.Value.GetAwaiter().GetResult();
return base.WithImage(image);
}
/// <summary> /// <summary>
/// Set the admin username and password for the container (basic authentication). /// Set the admin username and password for the container (basic authentication).
/// </summary> /// </summary>
@@ -112,6 +137,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
{ {
DockerResourceConfiguration.WithWatchStaticMappings(includeSubDirectories); DockerResourceConfiguration.WithWatchStaticMappings(includeSubDirectories);
return return
WithCommand("--ReadStaticMappings true").
WithCommand("--WatchStaticMappings true"). WithCommand("--WatchStaticMappings true").
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories); WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
} }
@@ -129,9 +155,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
DockerResourceConfiguration.WithStaticMappingsPath(path); DockerResourceConfiguration.WithStaticMappingsPath(path);
return return WithWatchStaticMappings(includeSubDirectories);
WithReadStaticMappings().
WithCommand("--WatchStaticMappingsInSubdirectories", includeSubDirectories);
} }
/// <summary> /// <summary>
@@ -208,9 +232,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
// In case the _imageOS is not set, determine it from the Image FullName. // In case the _imageOS is not set, determine it from the Image FullName.
if (_imageOS == null) if (_imageOS == null)
{ {
if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock.net", StringComparison.OrdinalIgnoreCase) < 0) if (builder.DockerResourceConfiguration.Image.FullName.IndexOf("wiremock", StringComparison.OrdinalIgnoreCase) < 0)
{ {
throw new InvalidOperationException(); throw new InvalidOperationException("It's only possible to use a wiremock docker image.");
} }
_imageOS = builder.DockerResourceConfiguration.Image.FullName.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux; _imageOS = builder.DockerResourceConfiguration.Image.FullName.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0 ? OSPlatform.Windows : OSPlatform.Linux;
@@ -228,6 +252,25 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
builder.Validate(); builder.Validate();
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
builder = builder
.WithWaitStrategy(waitForContainerOS
.UntilMessageIsLogged("WireMock.Net server running", waitStrategy => waitStrategy.WithTimeout(TimeSpan.FromSeconds(30)))
.UntilHttpRequestIsSucceeded(httpWaitStrategy => httpWaitStrategy
.ForPort(WireMockContainer.ContainerPort)
.WithMethod(HttpMethod.Get)
.WithBasicAuthentication(DockerResourceConfiguration)
.ForPath("/__admin/health")
.ForStatusCode(HttpStatusCode.OK)
.ForResponseMessageMatching(async httpResponseMessage =>
{
var content = await httpResponseMessage.Content.ReadAsStringAsync();
return content?.Contains("Healthy") == true;
})
)
.AddCustomWaitStrategy(new WireMockWaitStrategy())
);
return new WireMockContainer(builder.DockerResourceConfiguration); return new WireMockContainer(builder.DockerResourceConfiguration);
} }
@@ -236,11 +279,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder<WireMockContaine
{ {
var builder = base.Init(); var builder = base.Init();
var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
return builder return builder
.WithPortBinding(WireMockContainer.ContainerPort, true) .WithPortBinding(WireMockContainer.ContainerPort, true)
.WithCommand($"--WireMockLogger {DefaultLogger}") .WithCommand($"--WireMockLogger {DefaultLogger}");
.WithWaitStrategy(waitForContainerOS.UntilMessageIsLogged("WireMock.Net server running"));
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -0,0 +1,23 @@
// Copyright © WireMock.Net
using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;
namespace WireMock.Net.Testcontainers;
internal class WireMockWaitStrategy : IWaitUntil
{
public async Task<bool> UntilAsync(IContainer container)
{
if (container is not WireMockContainer wireMockContainer)
{
throw new InvalidOperationException("The passed container is not a WireMockContainer.");
}
await wireMockContainer.CallAdditionalActionsAfterReadyAsync();
return true;
}
}
@@ -0,0 +1,79 @@
// Copyright © WireMock.Net
using System;
using Newtonsoft.Json;
using Stef.Validation;
using WireMock.Admin.Requests;
using WireMock.Logging;
using Xunit;
namespace WireMock.Net.Xunit;
/// <summary>
/// When using xUnit, this class enables to log the output from WireMock.Net to the <see cref="ITestOutputHelper"/>.
/// </summary>
public sealed class TestOutputHelperWireMockLogger : IWireMockLogger
{
private readonly ITestOutputHelper _testOutputHelper;
/// <summary>
/// Create a new instance on the <see cref="TestOutputHelperWireMockLogger"/>.
/// </summary>
/// <param name="testOutputHelper">Represents a class which can be used to provide test output.</param>
public TestOutputHelperWireMockLogger(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = Guard.NotNull(testOutputHelper);
}
/// <inheritdoc />
public void Debug(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Debug", formatString, args));
}
/// <inheritdoc />
public void Info(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Info", formatString, args));
}
/// <inheritdoc />
public void Warn(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Warning", formatString, args));
}
/// <inheritdoc />
public void Error(string formatString, params object[] args)
{
_testOutputHelper.WriteLine(Format("Error", formatString, args));
}
/// <inheritdoc />
public void Error(string message, Exception exception)
{
_testOutputHelper.WriteLine(Format("Error", $"{message} {{0}}", exception));
if (exception is AggregateException ae)
{
ae.Handle(ex =>
{
_testOutputHelper.WriteLine(Format("Error", "Exception {0}", ex));
return true;
});
}
}
/// <inheritdoc />
public void DebugRequestResponse(LogEntryModel logEntryModel, bool isAdminRequest)
{
var message = JsonConvert.SerializeObject(logEntryModel, Formatting.Indented);
_testOutputHelper.WriteLine(Format("DebugRequestResponse", "Admin[{0}] {1}", isAdminRequest, message));
}
private static string Format(string level, string formatString, params object[] args)
{
var message = args.Length > 0 ? string.Format(formatString, args) : formatString;
return $"{DateTime.UtcNow} [{level}] : {message}";
}
}
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Some extensions for xUnit (ITestOutputHelper)</Description>
<AssemblyTitle>WireMock.Net.xUnit.v3</AssemblyTitle>
<Authors>Stef Heyenrath</Authors>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>tdd;wiremock;test;unittest;xunit</PackageTags>
<ProjectGuid>{4F46BD02-BEBC-4B2D-B857-4169AD222267}</ProjectGuid>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../WireMock.Net/WireMock.Net.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Stef.Validation" Version="0.2.0" />
<PackageReference Include="xunit.v3.extensibility.core" Version="3.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WireMock.Net.Abstractions\WireMock.Net.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="JetBrains.Annotations" Version="2025.2.2" />
</ItemGroup>
</Project>
+5
View File
@@ -37,4 +37,9 @@
<ProjectReference Include="../WireMock.Net.GraphQL/WireMock.Net.GraphQL.csproj" /> <ProjectReference Include="../WireMock.Net.GraphQL/WireMock.Net.GraphQL.csproj" />
<ProjectReference Include="../WireMock.Net.ProtoBuf/WireMock.Net.ProtoBuf.csproj" /> <ProjectReference Include="../WireMock.Net.ProtoBuf/WireMock.Net.ProtoBuf.csproj" />
</ItemGroup> </ItemGroup>
<!-- OpenTelemetry exporter for .NET 6+ -->
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<ProjectReference Include="..\WireMock.Net.OpenTelemetry\WireMock.Net.OpenTelemetry.csproj" />
</ItemGroup>
</Project> </Project>
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.2.0" /> <Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@@ -19,7 +19,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" /> <PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -19,6 +19,7 @@ public class IntegrationTests(ITestOutputHelper output)
var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>(); var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>();
await using var app = await appHostBuilder.BuildAsync(); await using var app = await appHostBuilder.BuildAsync();
await app.StartAsync(); await app.StartAsync();
await app.ResourceNotifications.WaitForResourceHealthyAsync("wiremock-service");
using var httpClient = app.CreateHttpClient("wiremock-service"); using var httpClient = app.CreateHttpClient("wiremock-service");
@@ -46,6 +47,7 @@ public class IntegrationTests(ITestOutputHelper output)
var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>(); var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync<WireMock_Net_Aspire_TestAppHost>();
await using var app = await appHostBuilder.BuildAsync(); await using var app = await appHostBuilder.BuildAsync();
await app.StartAsync(); await app.StartAsync();
await app.ResourceNotifications.WaitForResourceHealthyAsync("wiremock-service");
var adminClient = app.CreateWireMockAdminClient("wiremock-service"); var adminClient = app.CreateWireMockAdminClient("wiremock-service");
@@ -13,7 +13,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.2.0" /> <PackageReference Include="Aspire.Hosting.Testing" Version="13.1.0" />
<PackageReference Include="Codecov" Version="1.13.0" /> <PackageReference Include="Codecov" Version="1.13.0" />
<PackageReference Include="coverlet.msbuild" Version="6.0.2"> <PackageReference Include="coverlet.msbuild" Version="6.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -13,7 +13,7 @@ public class WireMockServerArgumentsTests
var args = new WireMockServerArguments(); var args = new WireMockServerArguments();
// Assert // Assert
args.HttpPort.Should().BeNull(); args.HttpPorts.Should().BeEmpty();
args.AdminUsername.Should().BeNull(); args.AdminUsername.Should().BeNull();
args.AdminPassword.Should().BeNull(); args.AdminPassword.Should().BeNull();
args.ReadStaticMappings.Should().BeFalse(); args.ReadStaticMappings.Should().BeFalse();
@@ -3,6 +3,7 @@
using System.Net.Sockets; using System.Net.Sockets;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using WireMock.Util;
namespace WireMock.Net.Aspire.Tests; namespace WireMock.Net.Aspire.Tests;
@@ -40,7 +41,21 @@ public class WireMockServerBuilderExtensionsTests
} }
[Fact] [Fact]
public void AddWireMock() public void AddWireMock_WithInvalidAdditionalUrls_ShouldThrowArgumentException()
{
// Arrange
string[] invalidUrls = { "err" };
var builder = Mock.Of<IDistributedApplicationBuilder>();
// Act
Action act = () => builder.AddWireMock("ValidName", invalidUrls);
// Assert
act.Should().Throw<ArgumentException>().WithMessage("The URL 'err' is not valid.");
}
[Fact]
public void AddWireMockWithPort()
{ {
// Arrange // Arrange
var name = $"apiservice{Guid.NewGuid()}"; var name = $"apiservice{Guid.NewGuid()}";
@@ -65,9 +80,9 @@ public class WireMockServerBuilderExtensionsTests
ReadStaticMappings = true, ReadStaticMappings = true,
WatchStaticMappings = false, WatchStaticMappings = false,
MappingsPath = null, MappingsPath = null,
HttpPort = port HttpPorts = [port]
}); });
wiremock.Resource.Annotations.Should().HaveCount(5); wiremock.Resource.Annotations.Should().HaveCount(6);
var containerImageAnnotation = wiremock.Resource.Annotations.OfType<ContainerImageAnnotation>().FirstOrDefault(); var containerImageAnnotation = wiremock.Resource.Annotations.OfType<ContainerImageAnnotation>().FirstOrDefault();
containerImageAnnotation.Should().BeEquivalentTo(new ContainerImageAnnotation containerImageAnnotation.Should().BeEquivalentTo(new ContainerImageAnnotation
@@ -90,9 +105,90 @@ public class WireMockServerBuilderExtensionsTests
)); ));
wiremock.Resource.Annotations.OfType<EnvironmentCallbackAnnotation>().FirstOrDefault().Should().NotBeNull(); wiremock.Resource.Annotations.OfType<EnvironmentCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
wiremock.Resource.Annotations.OfType<CommandLineArgsCallbackAnnotation>().FirstOrDefault().Should().NotBeNull(); wiremock.Resource.Annotations.OfType<CommandLineArgsCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
wiremock.Resource.Annotations.OfType<ResourceCommandAnnotation>().FirstOrDefault().Should().NotBeNull();
}
[Fact]
public void AddWireMockWithAdditionalUrls()
{
// Arrange
var name = $"apiservice{Guid.NewGuid()}";
var freePorts = PortUtils.FindFreeTcpPorts(2).ToList();
string[] additionalUrls = { $"http://*:{freePorts[0]}", $"grpc://*:{freePorts[1]}" };
const string username = "admin";
const string password = "test";
var builder = DistributedApplication.CreateBuilder();
// Act
var wiremock = builder
.AddWireMock(name, additionalUrls)
.WithAdminUserNameAndPassword(username, password)
.WithReadStaticMappings();
// Assert
wiremock.Resource.Should().NotBeNull();
wiremock.Resource.Name.Should().Be(name);
wiremock.Resource.Arguments.Should().BeEquivalentTo(new WireMockServerArguments
{
AdminPassword = password,
AdminUsername = username,
ReadStaticMappings = true,
WatchStaticMappings = false,
MappingsPath = null,
HttpPorts = freePorts,
AdditionalUrls = additionalUrls.ToList()
});
wiremock.Resource.Annotations.Should().HaveCount(9);
var containerImageAnnotation = wiremock.Resource.Annotations.OfType<ContainerImageAnnotation>().FirstOrDefault();
containerImageAnnotation.Should().BeEquivalentTo(new ContainerImageAnnotation
{
Image = "sheyenrath/wiremock.net-alpine",
Registry = null,
Tag = "latest"
});
var endpointAnnotations = wiremock.Resource.Annotations.OfType<EndpointAnnotation>().ToArray();
endpointAnnotations.Should().HaveCount(3);
var endpointAnnotationForHttp80 = endpointAnnotations[0];
endpointAnnotationForHttp80.Should().BeEquivalentTo(new EndpointAnnotation(
protocol: ProtocolType.Tcp,
uriScheme: "http",
transport: null,
name: null,
port: null,
targetPort: 80,
isExternal: null,
isProxied: true
));
var endpointAnnotationForHttpFreePort = endpointAnnotations[1];
endpointAnnotationForHttpFreePort.Should().BeEquivalentTo(new EndpointAnnotation(
protocol: ProtocolType.Tcp,
uriScheme: "http",
transport: null,
name: $"http-{freePorts[0]}",
port: freePorts[0],
targetPort: freePorts[0],
isExternal: null,
isProxied: true
));
var endpointAnnotationForGrpcFreePort = endpointAnnotations[2];
endpointAnnotationForGrpcFreePort.Should().BeEquivalentTo(new EndpointAnnotation(
protocol: ProtocolType.Tcp,
uriScheme: "grpc",
transport: null,
name: $"grpc-{freePorts[1]}",
port: freePorts[1],
targetPort: freePorts[1],
isExternal: null,
isProxied: true
));
wiremock.Resource.Annotations.OfType<EnvironmentCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
wiremock.Resource.Annotations.OfType<CommandLineArgsCallbackAnnotation>().FirstOrDefault().Should().NotBeNull();
wiremock.Resource.Annotations.OfType<ResourceCommandAnnotation>().FirstOrDefault().Should().NotBeNull(); wiremock.Resource.Annotations.OfType<ResourceCommandAnnotation>().FirstOrDefault().Should().NotBeNull();
} }
} }
@@ -190,5 +190,92 @@
BodyDestination: SameAsSource, BodyDestination: SameAsSource,
Body: Buy milk Body: Buy milk
} }
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931006,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /delay,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
Delay: 1000
}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931007,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /random-delay,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
MinimumRandomDelay: 1234,
MaximumRandomDelay: 60000
}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931008,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /prob,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
StatusCode: 300
},
Probability: 0.1
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931009,
UpdatedAt: 2023-01-14 15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /prob,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
StatusCode: 201
},
Probability: 0.9
} }
] ]
@@ -78,3 +78,45 @@ builder
.WithBody("Buy milk") .WithBody("Buy milk")
); );
builder
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/delay", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931006")
.RespondWith(Response.Create()
.WithDelay(1000)
);
builder
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/random-delay", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931007")
.RespondWith(Response.Create()
.WithRandomDelay(1234, 60000)
);
builder
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931008")
.WithProbability(0.1)
.RespondWith(Response.Create()
.WithStatusCode(300)
);
builder
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931009")
.WithProbability(0.9)
.RespondWith(Response.Create()
.WithStatusCode(201)
);
@@ -78,3 +78,45 @@ server
.WithBody("Buy milk") .WithBody("Buy milk")
); );
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/delay", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931006")
.RespondWith(Response.Create()
.WithDelay(1000)
);
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/random-delay", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931007")
.RespondWith(Response.Create()
.WithRandomDelay(1234, 60000)
);
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931008")
.WithProbability(0.1)
.RespondWith(Response.Create()
.WithStatusCode(300)
);
server
.Given(Request.Create()
.UsingMethod("GET")
.WithPath(new WildcardMatcher(WireMock.Matchers.MatchBehaviour.AcceptOnMatch, "/prob", false, WireMock.Matchers.MatchOperator.Or))
)
.WithGuid("98fae52e-76df-47d9-876f-2ee32e931009")
.WithProbability(0.9)
.RespondWith(Response.Create()
.WithStatusCode(201)
);
@@ -186,5 +186,92 @@
BodyDestination: SameAsSource, BodyDestination: SameAsSource,
Body: Buy milk Body: Buy milk
} }
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931006,
UpdatedAt: 2023-01-14T15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /delay,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
Delay: 1000
}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931007,
UpdatedAt: 2023-01-14T15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /random-delay,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
MinimumRandomDelay: 1234,
MaximumRandomDelay: 60000
}
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931008,
UpdatedAt: 2023-01-14T15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /prob,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
StatusCode: 300
},
Probability: 0.1
},
{
Guid: 98fae52e-76df-47d9-876f-2ee32e931009,
UpdatedAt: 2023-01-14T15:16:17,
Request: {
Path: {
Matchers: [
{
Name: WildcardMatcher,
Pattern: /prob,
IgnoreCase: false
}
]
},
Methods: [
GET
]
},
Response: {
StatusCode: 201
},
Probability: 0.9
} }
] ]
@@ -2,6 +2,7 @@
#if !(NET452 || NET461 || NETCOREAPP3_1) #if !(NET452 || NET461 || NETCOREAPP3_1)
using System; using System;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using Moq; using Moq;
using VerifyTests; using VerifyTests;
@@ -118,6 +119,35 @@ public class MappingBuilderTests
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithBody("Buy milk")); .WithBody("Buy milk"));
_sut.Given(Request.Create()
.WithPath("/delay")
.UsingGet()
).RespondWith(Response.Create()
.WithDelay(1000)
);
_sut.Given(Request.Create()
.WithPath("/random-delay")
.UsingGet()
).RespondWith(Response.Create()
.WithRandomDelay(1234)
);
_sut.Given(Request.Create()
.WithPath("/prob")
.UsingGet()
).WithProbability(0.1)
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.Ambiguous)
);
_sut.Given(Request.Create()
.WithPath("/prob")
.UsingGet()
).WithProbability(0.9)
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.Created)
);
_numMappings = _sut.GetMappings().Length; _numMappings = _sut.GetMappings().Length;
} }
@@ -0,0 +1,42 @@
// Copyright © WireMock.Net
#if NET6_0_OR_GREATER
using System;
using FluentAssertions;
using WireMock.OpenTelemetry;
using Xunit;
namespace WireMock.Net.Tests.OpenTelemetry;
public class OpenTelemetryOptionsParserTests
{
[Fact]
public void TryParseArguments_Enabled_ShouldReturnOptions()
{
// Act
var result = OpenTelemetryOptionsParser.TryParseArguments(new[]
{
"--OpenTelemetryEnabled", "true",
"--OpenTelemetryExcludeAdminRequests", "false",
"--OpenTelemetryOtlpExporterEndpoint", "http://localhost:4317"
}, null, out var options);
// Assert
result.Should().BeTrue();
options.Should().NotBeNull();
options!.ExcludeAdminRequests.Should().BeFalse();
options.OtlpExporterEndpoint.Should().Be("http://localhost:4317");
}
[Fact]
public void TryParseArguments_NotEnabled_ShouldReturnNull()
{
// Act
var result = OpenTelemetryOptionsParser.TryParseArguments(Array.Empty<string>(), null, out var options);
// Assert
result.Should().BeTrue();
options.Should().BeNull();
}
}
#endif
@@ -0,0 +1,43 @@
// Copyright © WireMock.Net
#if NET6_0_OR_GREATER
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using WireMock.OpenTelemetry;
using Xunit;
namespace WireMock.Net.Tests.OpenTelemetry;
public class WireMockOpenTelemetryExtensionsTests
{
[Fact]
public void AddWireMockOpenTelemetry_WithNullOptions_ShouldNotAddServices()
{
// Arrange
var services = new ServiceCollection();
var initialCount = services.Count;
// Act
var result = services.AddWireMockOpenTelemetry(null);
// Assert
result.Should().BeSameAs(services);
services.Count.Should().Be(initialCount);
}
[Fact]
public void AddWireMockOpenTelemetry_WithOptions_ShouldAddServices()
{
// Arrange
var services = new ServiceCollection();
var initialCount = services.Count;
// Act
var result = services.AddWireMockOpenTelemetry(new OpenTelemetryOptions());
// Assert
result.Should().BeSameAs(services);
services.Count.Should().BeGreaterThan(initialCount);
}
}
#endif
@@ -0,0 +1,195 @@
// Copyright © WireMock.Net
#if NET6_0_OR_GREATER
using System;
using System.Diagnostics;
using System.Linq;
using FluentAssertions;
using Moq;
using WireMock.Logging;
using WireMock.Matchers.Request;
using WireMock.Models;
using WireMock.Owin.ActivityTracing;
using WireMock.Util;
using Xunit;
namespace WireMock.Net.Tests.Owin.ActivityTracing;
public class WireMockActivitySourceTests
{
[Fact]
public void EnrichWithRequest_ShouldSetRequestTagsAndBody_WhenEnabled()
{
// Arrange
using var activity = new Activity("test").Start();
var request = new RequestMessage(
new UrlDetails("http://localhost/api/orders"),
"POST",
"127.0.0.1",
new BodyData { BodyAsString = "payload" });
var options = new ActivityTracingOptions
{
RecordRequestBody = true
};
// Act
WireMockActivitySource.EnrichWithRequest(activity, request, options);
// Assert
activity.GetTagItem(WireMockSemanticConventions.HttpMethod).Should().Be("POST");
activity.GetTagItem(WireMockSemanticConventions.HttpUrl).Should().Be("http://localhost/api/orders");
activity.GetTagItem(WireMockSemanticConventions.HttpPath).Should().Be("/api/orders");
activity.GetTagItem(WireMockSemanticConventions.HttpHost).Should().Be("localhost");
activity.GetTagItem(WireMockSemanticConventions.ClientAddress).Should().Be("127.0.0.1");
activity.GetTagItem(WireMockSemanticConventions.RequestBody).Should().Be("payload");
}
[Fact]
public void EnrichWithResponse_ShouldSetStatusAndBody_WhenEnabled()
{
// Arrange
using var activity = new Activity("test").Start();
var response = new ResponseMessage
{
StatusCode = 200,
BodyData = new BodyData { BodyAsString = "ok" }
};
var options = new ActivityTracingOptions
{
RecordResponseBody = true
};
// Act
WireMockActivitySource.EnrichWithResponse(activity, response, options);
// Assert
activity.GetTagItem(WireMockSemanticConventions.HttpStatusCode).Should().Be(200);
activity.GetTagItem("otel.status_code").Should().Be("OK");
activity.GetTagItem(WireMockSemanticConventions.ResponseBody).Should().Be("ok");
}
[Fact]
public void EnrichWithResponse_ShouldSetErrorStatus_ForNonSuccess()
{
// Arrange
using var activity = new Activity("test").Start();
var response = new ResponseMessage
{
StatusCode = 500
};
// Act
WireMockActivitySource.EnrichWithResponse(activity, response, new ActivityTracingOptions());
// Assert
activity.GetTagItem(WireMockSemanticConventions.HttpStatusCode).Should().Be(500);
activity.GetTagItem("otel.status_code").Should().Be("ERROR");
}
[Fact]
public void EnrichWithRequest_ShouldNotRecordBody_WhenDisabled()
{
// Arrange
using var activity = new Activity("test").Start();
var request = new RequestMessage(
new UrlDetails("http://localhost/api/orders"),
"POST",
"127.0.0.1",
new BodyData { BodyAsString = "payload" });
var options = new ActivityTracingOptions
{
RecordRequestBody = false
};
// Act
WireMockActivitySource.EnrichWithRequest(activity, request, options);
// Assert
activity.GetTagItem(WireMockSemanticConventions.RequestBody).Should().BeNull();
}
[Fact]
public void EnrichWithResponse_ShouldNotRecordBody_WhenDisabled()
{
// Arrange
using var activity = new Activity("test").Start();
var response = new ResponseMessage
{
StatusCode = 200,
BodyData = new BodyData { BodyAsString = "ok" }
};
var options = new ActivityTracingOptions
{
RecordResponseBody = false
};
// Act
WireMockActivitySource.EnrichWithResponse(activity, response, options);
// Assert
activity.GetTagItem(WireMockSemanticConventions.ResponseBody).Should().BeNull();
}
[Fact]
public void EnrichWithLogEntry_ShouldSkipMatchDetails_WhenDisabled()
{
// Arrange
using var activity = new Activity("test").Start();
var request = new RequestMessage(
new UrlDetails("http://localhost/api/orders"),
"GET",
"127.0.0.1");
var response = new ResponseMessage { StatusCode = 200 };
var matchResult = new Mock<IRequestMatchResult>();
matchResult.SetupGet(r => r.IsPerfectMatch).Returns(true);
matchResult.SetupGet(r => r.TotalScore).Returns(1.0);
var logEntry = new LogEntry
{
Guid = Guid.NewGuid(),
RequestMessage = request,
ResponseMessage = response,
RequestMatchResult = matchResult.Object,
MappingGuid = Guid.NewGuid(),
MappingTitle = "test-mapping"
};
var options = new ActivityTracingOptions
{
RecordMatchDetails = false
};
// Act
WireMockActivitySource.EnrichWithLogEntry(activity, logEntry, options);
// Assert
activity.GetTagItem(WireMockSemanticConventions.RequestGuid).Should().Be(logEntry.Guid.ToString());
activity.Tags.Should().NotContain(tag => tag.Key == WireMockSemanticConventions.MappingGuid);
activity.Tags.Should().NotContain(tag => tag.Key == WireMockSemanticConventions.MappingTitle);
activity.Tags.Should().NotContain(tag => tag.Key == WireMockSemanticConventions.MatchScore);
}
[Fact]
public void RecordException_ShouldSetExceptionTags()
{
// Arrange
using var activity = new Activity("test").Start();
var exception = new InvalidOperationException("boom");
// Act
WireMockActivitySource.RecordException(activity, exception);
// Assert
activity.GetTagItem("otel.status_code").Should().Be("ERROR");
activity.GetTagItem("otel.status_description").Should().Be("boom");
activity.GetTagItem("exception.type").Should().Be(typeof(InvalidOperationException).FullName);
activity.GetTagItem("exception.message").Should().Be("boom");
activity.GetTagItem("exception.stacktrace").Should().NotBeNull();
}
}
#endif
@@ -13,6 +13,9 @@ using WireMock.Util;
using WireMock.Logging; using WireMock.Logging;
using WireMock.Matchers; using WireMock.Matchers;
using System.Collections.Generic; using System.Collections.Generic;
#if NET6_0_OR_GREATER
using System.Diagnostics;
#endif
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Admin.Requests; using WireMock.Admin.Requests;
using WireMock.Settings; using WireMock.Settings;
@@ -21,6 +24,9 @@ using WireMock.Handlers;
using WireMock.Matchers.Request; using WireMock.Matchers.Request;
using WireMock.ResponseBuilders; using WireMock.ResponseBuilders;
using WireMock.RequestBuilders; using WireMock.RequestBuilders;
#if NET6_0_OR_GREATER
using WireMock.Owin.ActivityTracing;
#endif
#if NET452 #if NET452
using Microsoft.Owin; using Microsoft.Owin;
using IContext = Microsoft.Owin.IOwinContext; using IContext = Microsoft.Owin.IOwinContext;
@@ -289,4 +295,90 @@ public class WireMockMiddlewareTests
_mappings.Should().HaveCount(1); _mappings.Should().HaveCount(1);
} }
#if NET6_0_OR_GREATER
[Fact]
public async Task WireMockMiddleware_Invoke_AdminPath_WithExcludeAdminRequests_ShouldNotStartActivity()
{
// Arrange
var request = new RequestMessage(new UrlDetails("http://localhost/__admin/health"), "GET", "::1");
_requestMapperMock.Setup(m => m.MapAsync(It.IsAny<IRequest>(), It.IsAny<IWireMockMiddlewareOptions>())).ReturnsAsync(request);
_optionsMock.SetupGet(o => o.ActivityTracingOptions).Returns(new WireMock.Owin.ActivityTracing.ActivityTracingOptions
{
ExcludeAdminRequests = true
});
var activityStarted = false;
using var listener = new ActivityListener
{
ShouldListenTo = source => source.Name == WireMockActivitySource.SourceName,
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = _ => activityStarted = true
};
ActivitySource.AddActivityListener(listener);
// Act
await _sut.Invoke(_contextMock.Object).ConfigureAwait(false);
// Assert
activityStarted.Should().BeFalse();
}
[Fact]
public async Task WireMockMiddleware_Invoke_NonAdminPath_WithTracingEnabled_ShouldStartActivity()
{
// Arrange
var request = new RequestMessage(new UrlDetails("http://localhost/api/orders"), "GET", "::1");
_requestMapperMock.Setup(m => m.MapAsync(It.IsAny<IRequest>(), It.IsAny<IWireMockMiddlewareOptions>())).ReturnsAsync(request);
_optionsMock.SetupGet(o => o.ActivityTracingOptions).Returns(new WireMock.Owin.ActivityTracing.ActivityTracingOptions
{
ExcludeAdminRequests = true
});
var activityStarted = false;
using var listener = new ActivityListener
{
ShouldListenTo = source => source.Name == WireMockActivitySource.SourceName,
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = _ => activityStarted = true
};
ActivitySource.AddActivityListener(listener);
// Act
await _sut.Invoke(_contextMock.Object).ConfigureAwait(false);
// Assert
activityStarted.Should().BeTrue();
}
[Fact]
public async Task WireMockMiddleware_Invoke_NonAdminPath_WithoutTracingOptions_ShouldNotStartActivity()
{
// Arrange
var request = new RequestMessage(new UrlDetails("http://localhost/api/orders"), "GET", "::1");
_requestMapperMock.Setup(m => m.MapAsync(It.IsAny<IRequest>(), It.IsAny<IWireMockMiddlewareOptions>())).ReturnsAsync(request);
_optionsMock.SetupGet(o => o.ActivityTracingOptions).Returns((WireMock.Owin.ActivityTracing.ActivityTracingOptions?)null);
var activityStarted = false;
using var listener = new ActivityListener
{
ShouldListenTo = source => source.Name == WireMockActivitySource.SourceName,
Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = _ => activityStarted = true
};
ActivitySource.AddActivityListener(listener);
// Act
await _sut.Invoke(_contextMock.Object).ConfigureAwait(false);
// Assert
activityStarted.Should().BeFalse();
}
#endif
} }

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